diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 88ceaa46..58f1cc7e 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -22,6 +22,11 @@ jobs: - name: Checkout uses: actions/checkout@v2 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + - uses: actions/setup-node@v1 with: node-version: '18.x' @@ -42,7 +47,7 @@ jobs: run: | sudo apt-get update sudo apt-get install -y gawk sed shtool \ - libffi-dev yasm texinfo libgnutls28-dev gcc-multilib + libffi-dev yasm texinfo libgnutls28-dev gcc-multilib jq - name: Build dependencies run: | export CC=gcc-11 @@ -59,7 +64,7 @@ jobs: export TARGET=all mkdir -p build && cd build cmake .. -DCMAKE_BUILD_TYPE=RelWithDebInfo - + - name: Build run: | export CC=gcc-11 @@ -73,6 +78,7 @@ jobs: export CC=gcc-11 export CXX=g++-11 export TARGET=all + export CMAKE_BUILD_TYPE=Release cd deps ./clean.sh ./build.sh WITH_EMSCRIPTEN=1 @@ -87,11 +93,11 @@ jobs: cd ../.. mkdir -p build_em && cd build_em export LIBRARIES_ROOT=../deps/deps_inst/wasm32-emscripten/lib - emcmake cmake -DEMSCRIPTEN=ON -DBUILD_TESTS=OFF -DGMP_LIBRARY="$LIBRARIES_ROOT"/libgmp.a -DCRYPTOPP_LIBRARY="$LIBRARIES_ROOT"/libcrypto.a -DGMPXX_LIBRARY="$LIBRARIES_ROOT"/libgmpxx.a .. + emcmake cmake -DCMAKE_BUILD_TYPE=MinSizeRel -DEMSCRIPTEN=ON -DBUILD_TESTS=OFF -DGMP_LIBRARY="$LIBRARIES_ROOT"/libgmp.a -DCRYPTOPP_LIBRARY="$LIBRARIES_ROOT"/libcrypto.a -DGMPXX_LIBRARY="$LIBRARIES_ROOT"/libgmpxx.a .. emmake make -j$(nproc) cd .. cp build_em/threshold_encryption/encrypt.* node/ - + - name: Calculate version run: | export BRANCH=${GITHUB_REF##*/} @@ -106,12 +112,51 @@ jobs: env: ACTIONS_ALLOW_UNSECURE_COMMANDS: true + - name: Resolve t-encrypt version and library path + run: | + lib_path="${PWD}/build/threshold_encryption/libt_encrypt_python.so" + echo "T_ENCRYPT_LIB_PATH=${lib_path}" >> "$GITHUB_ENV" + + # Calculate Python package version + cd python + BASE_VERSION=$(python3 setup_t_encrypt.py --version) + cd .. + + PACKAGE_NAME="t-encrypt" + VERSION=$(bash ./scripts/calculate_version_pypi.sh $PACKAGE_NAME $BASE_VERSION ${{ env.BRANCH }}) + + echo "PACKAGE_VERSION=${VERSION}" >> "$GITHUB_ENV" + + echo "Using lib: ${lib_path}" + echo "Publishing t-encrypt version: ${VERSION}" + + - name: Build t-encrypt Python distributions + working-directory: python + env: + PACKAGE_VERSION: ${{ env.PACKAGE_VERSION }} + T_ENCRYPT_LIB_PATH: ${{ env.T_ENCRYPT_LIB_PATH }} + run: | + python -m pip install --upgrade pip + python -m pip install --upgrade setuptools wheel twine + CURRENT_VERSION="$(python setup_t_encrypt.py --version)" + sed -i "s/${CURRENT_VERSION}/${PACKAGE_VERSION}/g" setup_t_encrypt.py + python setup_t_encrypt.py sdist bdist_wheel + python -m twine check dist/* + + - name: Publish t-encrypt to PyPI + working-directory: python + env: + TWINE_USERNAME: ${{ secrets.PIP_USERNAME }} + TWINE_PASSWORD: ${{ secrets.PIP_PASSWORD }} + run: | + python -m twine upload dist/* + - name: Publish on npm if: github.ref != 'refs/heads/stable' run: | cd node export PACKAGE_NAME=$(jq -r '.name' package.json) - export JS_LIBRARY_BASE_VERSION="$(npm run --silent version)" + export JS_LIBRARY_BASE_VERSION="$(npm run --silent version)" export JS_LIBRARY_VERSION=$(bash ./../scripts/calculate_version_npm.sh $PACKAGE_NAME $JS_LIBRARY_BASE_VERSION $BRANCH) npm version $JS_LIBRARY_VERSION --no-git-tag-version npm publish --access public --tag ${{ env.BRANCH }} @@ -138,7 +183,7 @@ jobs: - name: Upload bls_glue to Release uses: actions/upload-release-asset@latest - env: + env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: upload_url: ${{ steps.create_release.outputs.upload_url }} @@ -148,7 +193,7 @@ jobs: - name: Upload hash_g1 to Release uses: actions/upload-release-asset@latest - env: + env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: upload_url: ${{ steps.create_release.outputs.upload_url }} @@ -158,7 +203,7 @@ jobs: - name: Upload verify_bls to Release uses: actions/upload-release-asset@latest - env: + env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: upload_url: ${{ steps.create_release.outputs.upload_url }} diff --git a/CMakeLists.txt b/CMakeLists.txt index b9d454df..90d49ee3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -272,10 +272,10 @@ if(LIBBLS_BUILD_TESTS) target_compile_options(${name} PRIVATE -Wno-format-overflow) endif() - target_link_libraries(${name} PRIVATE - bls - ${CRYPTOPP_LIBRARY} - boost_program_options + target_link_libraries(${name} PRIVATE + bls + ${CRYPTOPP_LIBRARY} + boost_program_options ${extra_libs} ${FOLLY_STATIC_LIBS} ) diff --git a/backends/interface/group/PointSerializer.hpp b/backends/interface/group/PointSerializer.hpp index 973f2433..9961d2f6 100644 --- a/backends/interface/group/PointSerializer.hpp +++ b/backends/interface/group/PointSerializer.hpp @@ -49,13 +49,19 @@ class PointSerializer { // ----------------------- String Serializations ----------------------- // std::string toString( Base base ) const { - if ( base != libBLS::Base::HEXA ) { - throw ThresholdUtils::IncorrectInput( - "Point serialization to string is only supported in HEXA base" ); - } - std::string out; - forEachAffineComponent( [&]( const FqElement& c, size_t ) { out += c.toString( base ); } ); + if ( base == libBLS::Base::HEXA ) { + // HEXA: concatenate fixed-width (64 char) hex strings + forEachAffineComponent( + [&]( const FqElement& c, size_t ) { out += c.toString( base ); } ); + } else { + // DEC: join with colon delimiter (variable length decimal strings) + forEachAffineComponent( [&]( const FqElement& c, size_t i ) { + if ( i > 0 ) + out += ":"; + out += c.toString( base ); + } ); + } return out; } diff --git a/backends/mcl/group/G2Point.cpp b/backends/mcl/group/G2Point.cpp index c8f8ccc5..8f92296f 100644 --- a/backends/mcl/group/G2Point.cpp +++ b/backends/mcl/group/G2Point.cpp @@ -195,33 +195,49 @@ G2Point G2Point::fromBytes( const std::vector< uint8_t >& bytes ) { } G2Point G2Point::fromString( const std::string& str, Base base ) { - if ( base != Base::HEXA ) { - throw ThresholdUtils::IncorrectInput( - "G2Point is currently only supported to be built from hexadecimal base string" ); - } - - const size_t elementStringSize = MAX_FIELD_ELEMENT_SIZE_BYTES * 2; - - if ( str.size() != STRING_HEXA_CHARS ) { - throw ThresholdUtils::IncorrectInput( "Wrong string size to convert to G2" ); - } - algebra::G2Point ret; - ret.value.z.clear(); ret.value.z.a = FqBackendType::one(); try { - trySettingFieldWithString( - ret.value.x.a, str.substr( 0 * elementStringSize, elementStringSize ), base ); - trySettingFieldWithString( - ret.value.x.b, str.substr( 1 * elementStringSize, elementStringSize ), base ); - trySettingFieldWithString( - ret.value.y.a, str.substr( 2 * elementStringSize, elementStringSize ), base ); - trySettingFieldWithString( - ret.value.y.b, str.substr( 3 * elementStringSize, std::string::npos ), base ); + if ( base == Base::HEXA ) { + // HEXA: fixed-width 64-char substrings (256 chars total) + const size_t elementStringSize = MAX_FIELD_ELEMENT_SIZE_BYTES * 2; + + if ( str.size() != STRING_HEXA_CHARS ) { + throw ThresholdUtils::IncorrectInput( "Wrong string size to convert to G2" ); + } + + trySettingFieldWithString( + ret.value.x.a, str.substr( 0 * elementStringSize, elementStringSize ), base ); + trySettingFieldWithString( + ret.value.x.b, str.substr( 1 * elementStringSize, elementStringSize ), base ); + trySettingFieldWithString( + ret.value.y.a, str.substr( 2 * elementStringSize, elementStringSize ), base ); + trySettingFieldWithString( + ret.value.y.b, str.substr( 3 * elementStringSize, std::string::npos ), base ); + } else { + // DEC: colon-delimited variable-length decimal strings + std::vector< std::string > parts; + size_t start = 0; + size_t end = 0; + while ( ( end = str.find( ':', start ) ) != std::string::npos ) { + parts.push_back( str.substr( start, end - start ) ); + start = end + 1; + } + parts.push_back( str.substr( start ) ); + + if ( parts.size() != G2_NUM_COMPONENTS_AFFINE ) { + throw ThresholdUtils::IncorrectInput( + "Wrong number of colon-delimited components for G2Point DEC string" ); + } + + trySettingFieldWithString( ret.value.x.a, parts[0], base ); + trySettingFieldWithString( ret.value.x.b, parts[1], base ); + trySettingFieldWithString( ret.value.y.a, parts[2], base ); + trySettingFieldWithString( ret.value.y.b, parts[3], base ); + } } catch ( const std::exception& e ) { - std::cout << "EXCEPTION: " << e.what() << std::endl; throw ThresholdUtils::IncorrectInput( std::string( "Failed to set G2Point components from string: " ) + e.what() ); } diff --git a/deps/build.sh b/deps/build.sh index b7470be3..9ee0e0b8 100755 --- a/deps/build.sh +++ b/deps/build.sh @@ -206,6 +206,9 @@ else WITH_GTEST=1 fi +# Set C++ standard version used throughout the build +CXX_STANDARD=20 + export CFLAGS="$CFLAGS -fPIC" export CXXFLAGS="$CXXFLAGS -fPIC" WITH_OPENSSL="yes" @@ -239,6 +242,11 @@ WITH_FF="yes" WITH_GMP="yes" WITH_MCL="yes" +if [ -z "${WITH_SGX}" ]; +then + WITH_SGX="no" +fi + if [ -z "${PARALLEL_COUNT}" ]; then PARALLEL_COUNT=$NUMBER_OF_CPU_CORES @@ -594,7 +602,32 @@ fi if [ "$WITH_BOOST" = "yes" ]; then echo -e "${COLOR_SEPARATOR}==================== ${COLOR_PROJECT_NAME}BOOST${COLOR_SEPARATOR} ========================================${COLOR_RESET}" - if [ ! -f "$INSTALL_ROOT/lib/libboost_program_options.a" ]; + + # Define Boost libraries based on build mode + BOOST_LIBS_EMSCRIPTEN="program_options" + BOOST_LIBS_NORMAL="system thread filesystem regex atomic context" + BOOST_LIBS_SKALED="iostreams fiber log chrono date_time" + + # Build the full library list based on mode + if [[ "${WITH_EMSCRIPTEN}" -eq 1 ]]; then + BOOST_LIBS_TO_CHECK="$BOOST_LIBS_EMSCRIPTEN" + else + BOOST_LIBS_TO_CHECK="$BOOST_LIBS_EMSCRIPTEN $BOOST_LIBS_NORMAL" + if [ "$SKALED_DEPS_CHAIN" = "1" ]; then + BOOST_LIBS_TO_CHECK="$BOOST_LIBS_TO_CHECK $BOOST_LIBS_SKALED" + fi + fi + + # Check if all required Boost libraries exist before skipping + BOOST_CHECK_FAILED=0 + for lib in $BOOST_LIBS_TO_CHECK; do + if [ ! -f "$INSTALL_ROOT/lib/libboost_${lib}.a" ]; then + BOOST_CHECK_FAILED=1 + break + fi + done + + if [ "$BOOST_CHECK_FAILED" = "1" ]; then env_restore cd "$SOURCES_ROOT" @@ -617,16 +650,19 @@ then fi cd ${BOOST_NAME} echo -e "${COLOR_INFO}configuring and building it${COLOR_DOTS}...${COLOR_RESET}" + + # Build BOOST_LIBRARIES string with commas for bootstrap.sh if [[ "${WITH_EMSCRIPTEN}" -eq 1 ]]; then - BOOST_LIBRARIES="program_options" + BOOST_LIBRARIES="${BOOST_LIBS_EMSCRIPTEN// /,}" else - BOOST_LIBRARIES="system,thread,filesystem,regex,atomic,program_options,context" + BOOST_LIBRARIES="${BOOST_LIBS_EMSCRIPTEN// /,},${BOOST_LIBS_NORMAL// /,}" if [ "$SKALED_DEPS_CHAIN" = "1" ]; then - BOOST_LIBRARIES="${BOOST_LIBRARIES},iostreams,fiber,log,chrono,date_time" + BOOST_LIBRARIES="${BOOST_LIBRARIES},${BOOST_LIBS_SKALED// /,}" fi fi + eval ./bootstrap.sh --prefix="$INSTALL_ROOT" --with-libraries="$BOOST_LIBRARIES" if [ "$DEBUG" = "1" ]; then @@ -642,13 +678,13 @@ then else if [ "$UNIX_SYSTEM_NAME" = "Darwin" ]; then - eval ./b2 cxxflags=-fPIC toolset=clang cxxstd=20 cflags=-fPIC "${PARALLEL_MAKE_OPTIONS}" --prefix="$INSTALL_ROOT" --layout=system variant=${variant} link=static threading=multi install + eval ./b2 cxxflags=-fPIC toolset=clang cxxstd=${CXX_STANDARD} cflags=-fPIC "${PARALLEL_MAKE_OPTIONS}" --prefix="$INSTALL_ROOT" --layout=system variant=${variant} link=static threading=multi install else if [[ "${WITH_EMSCRIPTEN}" -eq 1 ]]; then - eval ./b2 toolset=emscripten cxxflags=-fPIC cxxstd=20 cflags=-fPIC "${PARALLEL_MAKE_OPTIONS}" --prefix="$INSTALL_ROOT" --disable-icu --layout=system variant=${variant} link=static install + eval ./b2 toolset=emscripten cxxflags=-fPIC cxxstd=${CXX_STANDARD} cflags=-fPIC "${PARALLEL_MAKE_OPTIONS}" --prefix="$INSTALL_ROOT" --disable-icu --layout=system variant=${variant} link=static install else - eval ./b2 cxxflags=-fPIC cxxstd=20 cflags=-fPIC "${PARALLEL_MAKE_OPTIONS}" --prefix="$INSTALL_ROOT" --layout=system variant=${variant} link=static threading=multi install + eval ./b2 cxxflags=-fPIC cxxstd=${CXX_STANDARD} cflags=-fPIC "${PARALLEL_MAKE_OPTIONS}" --prefix="$INSTALL_ROOT" --layout=system variant=${variant} link=static threading=multi install fi fi fi @@ -818,6 +854,10 @@ fi if [ "$WITH_MCL" = "yes" ]; then echo -e "${COLOR_SEPARATOR}==================== ${COLOR_PROJECT_NAME}MCL${COLOR_SEPARATOR} ===========================================${COLOR_RESET}" + HOST_OBJ_DIR="obj_host" + HOST_LIB_DIR="lib_host" + SGX_OBJ_DIR="obj_sgx" + SGX_LIB_DIR="lib_sgx" if [ ! -f "$INSTALL_ROOT/lib/libmcl.a" ]; then env_restore cd "$SOURCES_ROOT" @@ -830,6 +870,7 @@ if [ "$WITH_MCL" = "yes" ]; then eval git fetch eval git checkout e67f9e6eab43116a14751bf7166c59d98728297a # v3.03 released in 10/08/2025 eval mkdir -p build + mkdir -p "${HOST_OBJ_DIR}" "${HOST_LIB_DIR}" "${SGX_OBJ_DIR}" "${SGX_LIB_DIR}" bin echo -e "${COLOR_INFO}building it${COLOR_DOTS}...${COLOR_RESET}" if [[ "${WITH_EMSCRIPTEN}" -eq 1 ]]; then @@ -849,18 +890,25 @@ if [ "$WITH_MCL" = "yes" ]; then eval emmake "$MAKE" install # install else + # Ensure we don't reuse objects from a prior SGX build (XBYAK off). + "$MAKE" clean || true + rm -rf "${HOST_OBJ_DIR}" "${HOST_LIB_DIR}" + mkdir -p "${HOST_OBJ_DIR}" "${HOST_LIB_DIR}" "$MAKE" \ DEBUG=0 \ ARCH=x86_64 \ MCL_USE_XBYAK=1 \ - MCL_BINT_ASM=0 \ + MCL_BINT_ASM=1 \ MCL_MSM=0 \ MCL_FP_BIT=256 \ MCL_FR_BIT=256 \ + OBJ_DIR="${HOST_OBJ_DIR}" \ + LIB_DIR="${HOST_LIB_DIR}" \ + CFLAGS_USER="-DMCL_BINT_ASM_X64=1" \ ${PARALLEL_MAKE_OPTIONS} - cp lib/libmcl.a "$INSTALL_ROOT/lib/" - cp lib/lishe256.a "$INSTALL_ROOT/lib/" + cp "${HOST_LIB_DIR}/libmcl.a" "$INSTALL_ROOT/lib/" + cp "${HOST_LIB_DIR}/lishe256.a" "$INSTALL_ROOT/lib/" cp -r include/mcl "$INSTALL_ROOT/include/" cp -r include/cybozu "$INSTALL_ROOT/include/" fi @@ -869,6 +917,53 @@ if [ "$WITH_MCL" = "yes" ]; then else echo -e "${COLOR_SUCCESS}SKIPPED${COLOR_RESET}" fi + + # Build SGX MCL (no Xbyak, LLVM AOT) + if [ "$WITH_SGX" = "yes" ]; then + # Strict check for LLVM tools + if [ -z "$(which clang)" ] || [ -z "$(which llvm-as)" ]; then + echo -e "${COLOR_ERROR}Error: SGX build requires clang and llvm-as (LLVM toolchain).${COLOR_RESET}" + exit 1 + fi + + if [ ! -f "$INSTALL_ROOT/lib/libmcl_sgx.a" ]; then + echo -e "${COLOR_INFO}Building MCL for SGX...${COLOR_RESET}" + cd "$SOURCES_ROOT/mcl" + + # Clean previous build artifacts + "$MAKE" clean || true + rm -rf "${SGX_OBJ_DIR}" "${SGX_LIB_DIR}" + mkdir -p "${SGX_OBJ_DIR}" "${SGX_LIB_DIR}" + + "$MAKE" \ + DEBUG=0 \ + ARCH=x86_64 \ + MCL_USE_XBYAK=0 \ + MCL_USE_LLVM=1 \ + MCL_BINT_ASM=1 \ + MCL_BINT_ASM_X64=0 \ + MCL_MSM=0 \ + MCL_FP_BIT=256 \ + MCL_FR_BIT=256 \ + MCL_DONT_USE_OPENMP=1 \ + OBJ_DIR="${SGX_OBJ_DIR}" \ + LIB_DIR="${SGX_LIB_DIR}" \ + CFLAGS_USER="-DMCL_DONT_USE_CSPRNG \ + -DCYBOZU_DONT_USE_STRING \ + -DCYBOZU_DONT_USE_EXCEPTION \ + -DCYBOZU_HOST_UNKNOWN=0 \ + -DCYBOZU_HOST_INTEL=1 \ + -DCYBOZU_HOST=0 \ + -Wa,--noexecstack" \ + ${PARALLEL_MAKE_OPTIONS} \ + "${SGX_LIB_DIR}/libmcl.a" + + cp "${SGX_LIB_DIR}/libmcl.a" "$INSTALL_ROOT/lib/libmcl_sgx.a" + cd "$SOURCES_ROOT" + else + echo -e "${COLOR_SUCCESS}SGX MCL SKIPPED${COLOR_RESET}" + fi + fi fi # ----------------------------------------------------------------------------- @@ -1187,6 +1282,7 @@ if [[ "${WITH_EMSCRIPTEN}" -eq 0 ]]; then eval mkdir -p build2 cd build2 eval "$CMAKE" "${CMAKE_CROSSCOMPILING_OPTS}" -DCMAKE_INSTALL_PREFIX="$INSTALL_ROOT" -DCMAKE_BUILD_TYPE="$TOP_CMAKE_BUILD_TYPE" \ + -DCMAKE_CXX_STANDARD=${CXX_STANDARD} \ -DBOOST_ROOT="$INSTALL_ROOT" -DBOOST_INCLUDEDIR="${INSTALL_ROOT}/include" -DBOOST_LIBRARYDIR="$INSTALL_ROOT/lib" \ -DBoost_NO_BOOST_CMAKE=ON -DBoost_NO_WARN_NEW_VERSIONS=1 -DBoost_DEBUG=ON \ -DBUILD_SHARED_LIBS=OFF \ @@ -1420,6 +1516,16 @@ then cd build $MAKE ${PARALLEL_MAKE_OPTIONS} $MAKE ${PARALLEL_MAKE_OPTIONS} install + + # Create jsoncpp symlink for jsonrpccpp compatibility + # jsonrpc looks for 'jsoncpp/json/json.h' + # but default install path is 'json/json.h' + # so we create jsoncpp/json -> json symlink + cd "$INSTALL_ROOT/include" + mkdir -p jsoncpp + cd jsoncpp + ln -sf ../json json + cd "$SOURCES_ROOT" else echo -e "${COLOR_SUCCESS}SKIPPED${COLOR_RESET}" diff --git a/node/EncryptMessage.js b/node/EncryptMessage.js index 12e564b4..0d7b11cc 100644 --- a/node/EncryptMessage.js +++ b/node/EncryptMessage.js @@ -1,22 +1,23 @@ const ModuleFactory = require('./encrypt.js'); -async function encryptMessage(txData, publicKey) { +async function encryptMessage(txData, publicKey, aadTE = '', aadAES = '') { const Module = await ModuleFactory(); return Module.ccall( 'encryptMessage', // Name of the exported C++ function 'string', // Return type - ['string', 'string'], // Argument types - [txData, publicKey] // Arguments + ['string', 'string', 'string', 'string'], // Argument types + [txData, publicKey, aadTE, aadAES] // Arguments ); } -async function encryptMessageDualKey(txData, firstPublicKey, secondPublicKey) { +async function encryptMessageDualKey( + txData, firstPublicKey, secondPublicKey, aadTE = '', aadAES = '') { const Module = await ModuleFactory(); return Module.ccall( 'encryptMessageDualKey', // Name of the exported C++ function 'string', // Return type - ['string', 'string', 'string'], // Argument types - [txData, firstPublicKey, secondPublicKey] // Arguments + ['string', 'string', 'string', 'string', 'string'], // Argument types + [txData, firstPublicKey, secondPublicKey, aadTE, aadAES] // Arguments ); } diff --git a/node/EncryptMessage.mjs b/node/EncryptMessage.mjs new file mode 100644 index 00000000..b723d087 --- /dev/null +++ b/node/EncryptMessage.mjs @@ -0,0 +1,32 @@ +import ModuleFactory from './encrypt.js'; + +export async function encryptMessage(txData, publicKey, aadTE = '', aadAES = '') { + const Module = await ModuleFactory(); + return Module.ccall( + 'encryptMessage', // Name of the exported C++ function + 'string', // Return type + ['string', 'string', 'string', 'string'], // Argument types + [txData, publicKey, aadTE, aadAES] // Arguments + ); +} + +export async function encryptMessageDualKey( + txData, firstPublicKey, secondPublicKey, aadTE = '', aadAES = '') { + const Module = await ModuleFactory(); + return Module.ccall( + 'encryptMessageDualKey', // Name of the exported C++ function + 'string', // Return type + ['string', 'string', 'string', 'string', 'string'], // Argument types + [txData, firstPublicKey, secondPublicKey, aadTE, aadAES] // Arguments + ); +} + +export async function encryptMessageMockup(txData) { + const Module = await ModuleFactory(); + return Module.ccall( + 'encryptMessageMockup', // Name of the exported C++ function + 'string', // Return type + ['string'], // Argument types + [txData] // Arguments + ); +} diff --git a/node/package.json b/node/package.json index 8114af9b..b5b258c9 100644 --- a/node/package.json +++ b/node/package.json @@ -1,6 +1,6 @@ { "name": "@skalenetwork/t-encrypt", - "version": "0.6.0", + "version": "0.8.0", "keywords": [ "SKALE", @@ -29,8 +29,17 @@ }, "description": "A Node.js module to encrypt messages using WebAssembly", "main": "EncryptMessage.js", + "module": "EncryptMessage.mjs", + "sideEffects": false, + "exports": { + ".": { + "import": "./EncryptMessage.mjs", + "require": "./EncryptMessage.js" + } + }, "files": [ "EncryptMessage.js", + "EncryptMessage.mjs", "encrypt.js", "encrypt.wasm" ] diff --git a/python/MANIFEST.in b/python/MANIFEST.in new file mode 100644 index 00000000..5f4a094b --- /dev/null +++ b/python/MANIFEST.in @@ -0,0 +1 @@ +recursive-include t_encrypt *.so diff --git a/python/setup.py b/python/setup.py index f8fb1461..df49ab1c 100755 --- a/python/setup.py +++ b/python/setup.py @@ -20,7 +20,8 @@ '../deps/deps_inst/x86_or_x64/include/libff'], library_dirs=['../build', '../deps/deps_inst/x86_or_x64/lib', - '../deps/deps_inst/x86_or_x64/lib/libff', '../deps/deps_inst/x86_or_x64/lib/libgmp', + '../deps/deps_inst/x86_or_x64/lib/libff', + '../deps/deps_inst/x86_or_x64/lib/libgmp', '../deps/deps_inst/x86_or_x64/lib/libgmpxx'], libraries=['bls', 'ff', 'gmpxx', 'gmp'] diff --git a/python/setup.sh b/python/setup.sh index 09cc4aa3..0fbea6cf 100755 --- a/python/setup.sh +++ b/python/setup.sh @@ -11,3 +11,12 @@ echo ================ module built ============= ldd ./build/lib.linux-x86_64-3.6/dkgpython.cpython-36m-x86_64-linux-gnu.so mv ./build/lib.linux-x86_64-3.6/dkgpython.cpython-36m-x86_64-linux-gnu.so dkgpython.so echo ================ setup done =============== + +echo ================ building t-encrypt =============== +# Note: This assumes libt_encrypt_python.so has been built in ../build/ via cmake +python3 $CWD/setup_t_encrypt.py install --user +if [[ $? -ne 0 ]] ; then + echo "Error installing t-encrypt. Ensure you have built the C++ library (make t_encrypt_python)" + exit 1 +fi +echo ================ setup t-encrypt done ============= diff --git a/python/setup_t_encrypt.py b/python/setup_t_encrypt.py new file mode 100755 index 00000000..c20525e1 --- /dev/null +++ b/python/setup_t_encrypt.py @@ -0,0 +1,60 @@ +""" +Setup script for the SKALE Threshold Encryption Python package. +""" +import os +import shutil +from setuptools import setup, find_packages +from setuptools.command.build_py import build_py + +# Configuration +PACKAGE_NAME = 't_encrypt' +LIB_NAME = 'libencrypt.so' +BUILD_TARGET_NAME = 'libt_encrypt_python.so' + + +def resolve_built_library_path() -> str: + override_path = os.environ.get('T_ENCRYPT_LIB_PATH') + if override_path: + if not os.path.exists(override_path): + raise FileNotFoundError( + f"T_ENCRYPT_LIB_PATH points to missing file: {override_path}" + ) + return override_path + else: + raise RuntimeError( + "Environment variable T_ENCRYPT_LIB_PATH is not set. ") + +class CustomBuildPy(build_py): + """ + Custom build command to copy the shared library into the package directory. + """ + def run(self): + current_dir = os.path.dirname(os.path.abspath(__file__)) + found_lib = resolve_built_library_path() + + target_path = os.path.join(current_dir, PACKAGE_NAME, LIB_NAME) + + shutil.copy2(found_lib, target_path) + os.chmod(target_path, 0o755) + + super().run() + +setup( + name='t-encrypt', + version='0.0.1', + description='Python bindings for SKALE Threshold Encryption', + author='SKALE Network', + packages=find_packages(), + include_package_data=True, + package_data={ + 't_encrypt': ['*.so'], + }, + cmdclass={ + 'build_py': CustomBuildPy, + }, + classifiers=[ + 'Programming Language :: Python :: 3', + 'Operating System :: POSIX :: Linux', + ], + python_requires='>=3.11', +) diff --git a/python/t_encrypt/__init__.py b/python/t_encrypt/__init__.py new file mode 100644 index 00000000..742e4d0b --- /dev/null +++ b/python/t_encrypt/__init__.py @@ -0,0 +1,15 @@ +""" +This module provides a Python interface to the t-encrypt C++ library. +It allows encrypting messages using BLS keys. +""" +from .core import encrypt_message, encrypt_message_dual_key, encrypt_message_mockup +from .exceptions import TEncryptError, LibraryNotFoundError, EncryptionError + +__all__ = [ + 'encrypt_message', + 'encrypt_message_dual_key', + 'encrypt_message_mockup', + 'TEncryptError', + 'LibraryNotFoundError', + 'EncryptionError', +] diff --git a/python/t_encrypt/core.py b/python/t_encrypt/core.py new file mode 100644 index 00000000..c5a487d4 --- /dev/null +++ b/python/t_encrypt/core.py @@ -0,0 +1,154 @@ +""" +Core implementation of SKALE Threshold Encryption interface. +""" +import ctypes +import os +from ctypes import c_char_p + +from .exceptions import EncryptionError, LibraryNotFoundError + +def _load_library(): + """Finds and loads the shared library, setting up function signatures.""" + # The library is expected to be in the same directory as this file + lib_path = os.path.join(os.path.dirname(__file__), 'libencrypt.so') + + if not os.path.exists(lib_path): + # Fallback for development/testing if not explicitly installed + # Check if we can find it in common build locations relative to this file + current_dir = os.path.dirname(__file__) + possible_paths = [ + os.path.abspath(os.path.join(current_dir, '../../build/threshold_encryption/libt_encrypt_python.so')), + ] + for path in possible_paths: + if os.path.exists(path): + lib_path = path + break + + if not os.path.exists(lib_path): + raise LibraryNotFoundError(f"Shared library not found. Checked {lib_path} and build directories.") + + try: + lib = ctypes.CDLL(lib_path) + except OSError as e: + raise LibraryNotFoundError(f"Could not load shared library at {lib_path}: {e}") from e + + # Define return types and argument types for C++ functions + lib.encryptMessage.restype = c_char_p + lib.encryptMessage.argtypes = [c_char_p, c_char_p, c_char_p, c_char_p] + + lib.encryptMessageDualKey.restype = c_char_p + lib.encryptMessageDualKey.argtypes = [c_char_p, c_char_p, c_char_p, c_char_p, c_char_p] + + lib.encryptMessageMockup.restype = c_char_p + lib.encryptMessageMockup.argtypes = [c_char_p] + + return lib + + +# Load the shared library +_lib = _load_library() + +def encrypt_message( + tx_data: str, + public_key: str, + additional_authenticated_data_aes: str | None = None, + additional_authenticated_data_te: str | None = None) -> str: + """ + Encrypts a message using a single BLS public key. + + Args: + tx_data (str): The transaction data hex string. + public_key (str): The BLS public key hex string. + additional_authenticated_data_aes (str | None, optional): AES additional authenticated data. + additional_authenticated_data_te (str | None, optional): TE additional authenticated data. + + Returns: + str: The encrypted message as a hex string. + + Raises: + ValueError: If tx_data or public_key are empty. + EncryptionError: If encryption fails. + """ + if not tx_data or not public_key: + raise ValueError("tx_data and public_key must not be empty") + + aad_aes = None + if additional_authenticated_data_aes: + aad_aes = additional_authenticated_data_aes.encode('utf-8') + + aad_te = None + if additional_authenticated_data_te: + aad_te = additional_authenticated_data_te.encode('utf-8') + + result = _lib.encryptMessage( + tx_data.encode('utf-8'), + public_key.encode('utf-8'), + aad_aes, + aad_te + ) + if result is None: + raise EncryptionError("Encryption failed. Check library logs for details.") + return result.decode('utf-8') + +def encrypt_message_dual_key( + tx_data: str, + first_key: str, + second_key: str, + additional_authenticated_data_aes: str | None = None, + additional_authenticated_data_te: str | None = None) -> str: + """ + Encrypts a message using two BLS public keys (dual-key encryption). + Args: + tx_data (str): The transaction data hex string to be encrypted. + first_key (str): The first BLS public key hex string. + second_key (str): The second BLS public key hex string. + additional_authenticated_data_aes (str | None, optional): AES additional authenticated data. + additional_authenticated_data_te (str | None, optional): TE additional authenticated data. + Returns: + str: The encrypted message as a hex string produced by dual-key encryption. + + Raises: + ValueError: If tx_data, first_key, or second_key are empty. + EncryptionError: If dual key encryption fails. + """ + if not tx_data or not first_key or not second_key: + raise ValueError("tx_data, first_key, and second_key must not be empty") + + aad_aes = None + if additional_authenticated_data_aes: + aad_aes = additional_authenticated_data_aes.encode('utf-8') + + aad_te = None + if additional_authenticated_data_te: + aad_te = additional_authenticated_data_te.encode('utf-8') + + result = _lib.encryptMessageDualKey( + tx_data.encode('utf-8'), + first_key.encode('utf-8'), + second_key.encode('utf-8'), + aad_aes, + aad_te + ) + if result is None: + raise EncryptionError("Dual key encryption failed. Check library logs for details.") + return result.decode('utf-8') + +def encrypt_message_mockup(tx_data: str) -> str: + """ + Mockup encryption for testing purposes. + Args: + tx_data (str): The transaction data hex string. + Returns: + str: The mockup encrypted message as a hex string. + + Raises: + ValueError: If tx_data is empty. + EncryptionError: If mockup encryption fails. + """ + if not tx_data: + raise ValueError("tx_data must not be empty") + + result = _lib.encryptMessageMockup(tx_data.encode('utf-8')) + if result is None: + raise EncryptionError("Mockup encryption failed. Check library logs for details.") + return result.decode('utf-8') diff --git a/python/t_encrypt/exceptions.py b/python/t_encrypt/exceptions.py new file mode 100644 index 00000000..2bb03aa8 --- /dev/null +++ b/python/t_encrypt/exceptions.py @@ -0,0 +1,11 @@ +class TEncryptError(Exception): + """Base exception for t-encrypt errors.""" + pass + +class LibraryNotFoundError(TEncryptError): + """Raised when the shared library cannot be found or loaded.""" + pass + +class EncryptionError(TEncryptError): + """Raised when encryption fails.""" + pass diff --git a/python/test.py b/python/test.py index 5ad730e8..bec4e32c 100755 --- a/python/test.py +++ b/python/test.py @@ -1,17 +1,11 @@ #!/usr/bin/env python3 # encoding: utf-8 -import dkgpython -from dkgpython import dkg - -import os -import sys import binascii -import json -import logging + +from dkgpython import dkg import coincurve -from time import sleep def bxor(b1, b2): parts = [] diff --git a/scripts/calculate_version_pypi.sh b/scripts/calculate_version_pypi.sh new file mode 100644 index 00000000..134a9dfc --- /dev/null +++ b/scripts/calculate_version_pypi.sh @@ -0,0 +1,62 @@ +#!/bin/bash + +# Script to calculate the next PyPI version based on existing versions. +# Usage: ./calculate_version_pypi.sh + +PACKAGE_NAME=$1 +BASE_VERSION=$2 +BRANCH=$3 + +if [ -z "$PACKAGE_NAME" ]; then + echo "Error: Package name is not provided." + exit 1 +fi + +if [ -z "$BASE_VERSION" ]; then + echo "Error: Base version is not provided." + exit 1 +fi + +if [ -z "$BRANCH" ]; then + echo "Error: Branch name is not provided." + exit 1 +fi + +# If on stable branch, just return the base version (stripped of pre-release suffixes if any) +if [ "$BRANCH" = "stable" ]; then + echo "$BASE_VERSION" + exit 0 +fi + +# Determine the pre-release label +LABEL="dev" +if [ "$BRANCH" = "beta" ]; then + LABEL="rc" +elif [ "$BRANCH" = "develop" ]; then + LABEL="dev" +fi + +# Fetch existing versions from PyPI +# We use the JSON API: https://pypi.org/pypi//json +REGISTRY_URL="https://pypi.org/pypi/${PACKAGE_NAME}/json" +EXISTING_VERSIONS=$(curl -s -L "${REGISTRY_URL}" | jq -r '.releases | keys | .[]' 2>/dev/null) + +# Loop to find the next available version +for (( VERSION_NUMBER=0; ; VERSION_NUMBER++ )); do + # PEP 440 compliant pre-release version: 1.2.3.dev0, 1.2.3rc1, etc. + # Note: 'dev' versions normally use dot separator or not: 1.2.3.dev0 or 1.2.3dev0 + # We will use dot notation for clarity: BASE.devN or BASE.rcN + + if [ "$LABEL" = "dev" ]; then + RESULT_VERSION="${BASE_VERSION}.dev${VERSION_NUMBER}" + else + RESULT_VERSION="${BASE_VERSION}${LABEL}${VERSION_NUMBER}" + fi + + echo "$EXISTING_VERSIONS" | grep -q "^${RESULT_VERSION}$" + if [ $? -ne 0 ]; then + # Version not found in existing versions + echo "$RESULT_VERSION" + break + fi +done diff --git a/scripts/run_emscripten_test.sh b/scripts/run_emscripten_test.sh index 7c32e086..4c78501e 100644 --- a/scripts/run_emscripten_test.sh +++ b/scripts/run_emscripten_test.sh @@ -25,9 +25,28 @@ for i in $(seq 1 $RUNS); do MESSAGE=$(cat message.txt) PUBLIC_BLS_KEY=$(cat bls_public_key.txt) SECRET_KEY=$(cat secret_key.txt) - node test.js $PUBLIC_BLS_KEY $MESSAGE > encrypted_data.txt - ENCRYPTED_DATA=$(cat encrypted_data.txt) - ./decrypt_message "$ENCRYPTED_DATA" "$SECRET_KEY" "$MESSAGE" 0 + + # Occasionally pass AAD parameters (every 3rd run) + if [ $((i % 3)) -eq 0 ]; then + # Generate 20 or 32 byte hex strings for AAD + AAD_LENGTH=$((RANDOM % 2)) + if [ $AAD_LENGTH -eq 0 ]; then + # 20 bytes + AAD_AES=$(openssl rand -hex 20) + AAD_TE=$(openssl rand -hex 20) + else + # 32 bytes + AAD_AES=$(openssl rand -hex 32) + AAD_TE=$(openssl rand -hex 32) + fi + node test.js $PUBLIC_BLS_KEY $MESSAGE $AAD_TE $AAD_AES > encrypted_data.txt + ENCRYPTED_DATA=$(cat encrypted_data.txt) + ./decrypt_message "$ENCRYPTED_DATA" "$SECRET_KEY" "$MESSAGE" 0 "$AAD_AES" "$AAD_TE" + else + node test.js $PUBLIC_BLS_KEY $MESSAGE > encrypted_data.txt + ENCRYPTED_DATA=$(cat encrypted_data.txt) + ./decrypt_message "$ENCRYPTED_DATA" "$SECRET_KEY" "$MESSAGE" 0 + fi # Clean up temp files generated in $ABS_BUILD_DIR/ rm -f message.txt bls_public_key.txt encrypted_data.txt @@ -39,10 +58,8 @@ for i in $(seq 1 $RUNS); do ./generate_bls_keys SECOND_PUBLIC_BLS_KEY=$(cat bls_public_key.txt) SECOND_SECRET_BLS_KEY=$(cat secret_key.txt) - node test2Keys.js $FIRST_PUBLIC_BLS_KEY $SECOND_PUBLIC_BLS_KEY $MESSAGE > encrypted_data.txt - ENCRYPTED_DATA=$(cat encrypted_data.txt) - # Randomly pick index 1 or 2 + # Randomly pick index 0 or 1 RANDOM_INDEX=$((RANDOM % 2)) if [ $RANDOM_INDEX -eq 0 ]; then SECRET_KEY_TO_USE=$FIRST_SECRET_BLS_KEY @@ -50,5 +67,25 @@ for i in $(seq 1 $RUNS); do SECRET_KEY_TO_USE=$SECOND_SECRET_BLS_KEY fi - ./decrypt_message "$ENCRYPTED_DATA" "$SECRET_KEY_TO_USE" "$MESSAGE" "$RANDOM_INDEX" + # Occasionally pass AAD parameters (every 3rd run) + if [ $((i % 3)) -eq 0 ]; then + # Generate 20 or 32 byte hex strings for AAD + AAD_LENGTH=$((RANDOM % 2)) + if [ $AAD_LENGTH -eq 0 ]; then + # 20 bytes + AAD_AES=$(openssl rand -hex 20) + AAD_TE=$(openssl rand -hex 20) + else + # 32 bytes + AAD_AES=$(openssl rand -hex 32) + AAD_TE=$(openssl rand -hex 32) + fi + node test2Keys.js $FIRST_PUBLIC_BLS_KEY $SECOND_PUBLIC_BLS_KEY $MESSAGE $AAD_TE $AAD_AES > encrypted_data.txt + ENCRYPTED_DATA=$(cat encrypted_data.txt) + ./decrypt_message "$ENCRYPTED_DATA" "$SECRET_KEY_TO_USE" "$MESSAGE" "$RANDOM_INDEX" "$AAD_AES" "$AAD_TE" + else + node test2Keys.js $FIRST_PUBLIC_BLS_KEY $SECOND_PUBLIC_BLS_KEY $MESSAGE > encrypted_data.txt + ENCRYPTED_DATA=$(cat encrypted_data.txt) + ./decrypt_message "$ENCRYPTED_DATA" "$SECRET_KEY_TO_USE" "$MESSAGE" "$RANDOM_INDEX" + fi done diff --git a/test/test.js b/test/test.js index a32751fa..d0de71d2 100644 --- a/test/test.js +++ b/test/test.js @@ -2,14 +2,16 @@ const ModuleFactory = require("./encrypt.js"); const BLS_PUBLIC_KEY = process.argv[2]; const TX_DATA = process.argv[3]; +const AAD_TE = process.argv[4] || ""; +const AAD_AES = process.argv[5] || ""; ModuleFactory().then((Module) => { // Use the Module object after it is initialized const result = Module.ccall( 'encryptMessage', // Name of the exported C++ function 'string', // Return type - ['string', 'string'], // Argument types - [TX_DATA, BLS_PUBLIC_KEY] // Arguments + ['string', 'string', 'string', 'string'], // Argument types + [TX_DATA, BLS_PUBLIC_KEY, AAD_TE, AAD_AES] // Arguments ); console.log(result); }).catch((error) => { diff --git a/test/test2Keys.js b/test/test2Keys.js index 78264b5f..9412aa1d 100644 --- a/test/test2Keys.js +++ b/test/test2Keys.js @@ -3,14 +3,16 @@ const ModuleFactory = require("./encrypt.js"); const FIRST_BLS_PUBLIC_KEY = process.argv[2]; const SECOND_BLS_PUBLIC_KEY = process.argv[3]; const TX_DATA = process.argv[4]; +const AAD_TE = process.argv[5] || ""; +const AAD_AES = process.argv[6] || ""; ModuleFactory().then((Module) => { // Use the Module object after it is initialized const result = Module.ccall( 'encryptMessageDualKey', // Name of the exported C++ function 'string', // Return type - ['string', 'string', 'string'], // Argument types - [TX_DATA, FIRST_BLS_PUBLIC_KEY, SECOND_BLS_PUBLIC_KEY] // Arguments + ['string', 'string', 'string', 'string', 'string'], // Argument types + [TX_DATA, FIRST_BLS_PUBLIC_KEY, SECOND_BLS_PUBLIC_KEY, AAD_TE, AAD_AES] // Arguments ); console.log(result); }).catch((error) => { diff --git a/test/test_TE_wrappers.cpp b/test/test_TE_wrappers.cpp index 73044f2d..a75877e2 100644 --- a/test/test_TE_wrappers.cpp +++ b/test/test_TE_wrappers.cpp @@ -77,6 +77,844 @@ BOOST_AUTO_TEST_CASE( TEMockupEncryption ) { BOOST_REQUIRE( message == decryptedMsg ); } +BOOST_AUTO_TEST_CASE( TEEncryptDecryptWithAAD ) { + // Test the full ThresholdEncryption::encrypt/decrypt flow with AAD + size_t numAll = 4; + size_t numSigned = 3; + + keys keys = generateKeys( numSigned, numAll ); + + std::vector< uint8_t > message = randomByteVec( 100 ); + + // Separate AADs for AES and TE with different values + std::vector< uint8_t > aadAES = { 0xDE, 0xAD, 0xBE, 0xEF, 0xCA, 0xFE, 0xBA, 0xBE }; + std::vector< uint8_t > aadTE = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08 }; + + // Encrypt with both AADs + libBLS::EncryptMetaData metaData; + metaData.associatedDataAesGcm = aadAES; + metaData.associatedDataTE = aadTE; + libBLS::Ciphertext cypher = + libBLS::ThresholdEncryption::encrypt( message, keys.commonPublic, metaData ); + + std::vector< libBLS::TEPublicKeyShare > public_key_shares; + for ( size_t i = 0; i < numAll; i++ ) { + public_key_shares.emplace_back( libBLS::TEPublicKeyShare( keys.secretKeys[i] ) ); + } + + for ( const auto& cipheredKey : cypher.getKeys() ) { + // Validate with TE AAD + libBLS::ThresholdEncryption::validateEncryption( cipheredKey, &aadTE ); + + libBLS::TEDecryptSet decrSet( numSigned, numAll ); + for ( size_t i = 0; i < numSigned; i++ ) { + libBLS::TEDecryptionShare decr_share = + libBLS::ThresholdEncryption::partialDecrypt( cipheredKey, keys.secretKeys[i] ); + libBLS::ThresholdEncryption::validateDecryptionShare( + cipheredKey, decr_share, public_key_shares[i], &aadTE ); + decrSet.addDecryptShare( decr_share ); + } + + libBLS::AES256Key key_decrypted = + libBLS::ThresholdEncryption::combineShares( cipheredKey, decrSet ); + + // Decrypt WITH the same AES AAD - should succeed + std::vector< uint8_t > decipheredMsg = + libBLS::ThresholdEncryption::decrypt( cypher, key_decrypted, aadAES ); + BOOST_REQUIRE( decipheredMsg == message ); + } +} + +BOOST_AUTO_TEST_CASE( TEEncryptDecryptWithWrongAAD ) { + // Test that decryption fails when AAD doesn't match + size_t numAll = 4; + size_t numSigned = 3; + + keys keys = generateKeys( numSigned, numAll ); + + std::vector< uint8_t > message = randomByteVec( 100 ); + + // AADs used for encryption + std::vector< uint8_t > aadAES = { 0xDE, 0xAD, 0xBE, 0xEF, 0xCA, 0xFE, 0xBA, 0xBE }; + std::vector< uint8_t > aadTE = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08 }; + // Wrong AADs for decryption/validation + std::vector< uint8_t > wrong_aadAES = { 0x11, 0x22, 0x33, 0x44 }; + std::vector< uint8_t > wrong_aadTE = { 0xAA, 0xBB, 0xCC, 0xDD }; + + // Encrypt with both AADs + libBLS::EncryptMetaData metaData; + metaData.associatedDataAesGcm = aadAES; + metaData.associatedDataTE = aadTE; + libBLS::Ciphertext cypher = + libBLS::ThresholdEncryption::encrypt( message, keys.commonPublic, metaData ); + + std::vector< libBLS::TEPublicKeyShare > public_key_shares; + for ( size_t i = 0; i < numAll; i++ ) { + public_key_shares.emplace_back( libBLS::TEPublicKeyShare( keys.secretKeys[i] ) ); + } + + for ( const auto& cipheredKey : cypher.getKeys() ) { + // Validate with wrong TE AAD - should fail + BOOST_REQUIRE_THROW( + libBLS::ThresholdEncryption::validateEncryption( cipheredKey, &wrong_aadTE ), + libBLS::ThresholdUtils::IsNotWellFormed ); + + // Validate with correct TE AAD - should succeed + libBLS::ThresholdEncryption::validateEncryption( cipheredKey, &aadTE ); + + libBLS::TEDecryptSet decrSet( numSigned, numAll ); + for ( size_t i = 0; i < numSigned; i++ ) { + libBLS::TEDecryptionShare decr_share = + libBLS::ThresholdEncryption::partialDecrypt( cipheredKey, keys.secretKeys[i] ); + decrSet.addDecryptShare( decr_share ); + } + + libBLS::AES256Key key_decrypted = + libBLS::ThresholdEncryption::combineShares( cipheredKey, decrSet ); + + // Decrypt with wrong AES AAD - should fail + BOOST_REQUIRE_THROW( + libBLS::ThresholdEncryption::decrypt( cypher, key_decrypted, wrong_aadAES ), + std::runtime_error ); + + // Decrypt without AES AAD - should also fail + BOOST_REQUIRE_THROW( + libBLS::ThresholdEncryption::decrypt( cypher, key_decrypted, std::nullopt ), + std::runtime_error ); + } +} + +BOOST_AUTO_TEST_CASE( TEValidateDecryptionShareWithWrongAAD ) { + // Test that validateDecryptionShare fails with wrong TE AAD + size_t numAll = 4; + size_t numSigned = 3; + + keys keys = generateKeys( numSigned, numAll ); + std::vector< uint8_t > message = randomByteVec( 100 ); + + std::vector< uint8_t > aadTE = { 0x01, 0x02, 0x03, 0x04 }; + std::vector< uint8_t > wrong_aadTE = { 0xAA, 0xBB, 0xCC, 0xDD }; + + libBLS::EncryptMetaData metaData; + metaData.associatedDataTE = aadTE; + libBLS::Ciphertext cypher = + libBLS::ThresholdEncryption::encrypt( message, keys.commonPublic, metaData ); + + std::vector< libBLS::TEPublicKeyShare > public_key_shares; + for ( size_t i = 0; i < numAll; i++ ) { + public_key_shares.emplace_back( libBLS::TEPublicKeyShare( keys.secretKeys[i] ) ); + } + + for ( const auto& cipheredKey : cypher.getKeys() ) { + libBLS::TEDecryptionShare decr_share = + libBLS::ThresholdEncryption::partialDecrypt( cipheredKey, keys.secretKeys[0] ); + + // Validate with correct TE AAD - should succeed + libBLS::ThresholdEncryption::validateDecryptionShare( + cipheredKey, decr_share, public_key_shares[0], &aadTE ); + + // Validate with wrong TE AAD - should fail + BOOST_REQUIRE_THROW( libBLS::ThresholdEncryption::validateDecryptionShare( + cipheredKey, decr_share, public_key_shares[0], &wrong_aadTE ), + libBLS::ThresholdUtils::IsNotWellFormed ); + + // Validate with nullptr AAD on AAD-encrypted ciphertext - should fail + BOOST_REQUIRE_THROW( libBLS::ThresholdEncryption::validateDecryptionShare( + cipheredKey, decr_share, public_key_shares[0], nullptr ), + libBLS::ThresholdUtils::IsNotWellFormed ); + } +} + +BOOST_AUTO_TEST_CASE( TEEncryptWithTEAADOnly ) { + // Test encryption with only TE AAD (no AES AAD) + size_t numAll = 4; + size_t numSigned = 3; + + keys keys = generateKeys( numSigned, numAll ); + std::vector< uint8_t > message = randomByteVec( 100 ); + + std::vector< uint8_t > aadTE = { 0x01, 0x02, 0x03, 0x04 }; + + libBLS::EncryptMetaData metaData; + metaData.associatedDataTE = aadTE; // Only TE AAD, no AES AAD + + libBLS::Ciphertext cypher = + libBLS::ThresholdEncryption::encrypt( message, keys.commonPublic, metaData ); + + std::vector< libBLS::TEPublicKeyShare > public_key_shares; + for ( size_t i = 0; i < numAll; i++ ) { + public_key_shares.emplace_back( libBLS::TEPublicKeyShare( keys.secretKeys[i] ) ); + } + + for ( const auto& cipheredKey : cypher.getKeys() ) { + // Validate encryption with TE AAD + libBLS::ThresholdEncryption::validateEncryption( cipheredKey, &aadTE ); + + libBLS::TEDecryptSet decrSet( numSigned, numAll ); + for ( size_t i = 0; i < numSigned; i++ ) { + libBLS::TEDecryptionShare decr_share = + libBLS::ThresholdEncryption::partialDecrypt( cipheredKey, keys.secretKeys[i] ); + libBLS::ThresholdEncryption::validateDecryptionShare( + cipheredKey, decr_share, public_key_shares[i], &aadTE ); + decrSet.addDecryptShare( decr_share ); + } + + libBLS::AES256Key key_decrypted = + libBLS::ThresholdEncryption::combineShares( cipheredKey, decrSet ); + + // Decrypt without AES AAD - should succeed since no AES AAD was used + std::vector< uint8_t > decipheredMsg = + libBLS::ThresholdEncryption::decrypt( cypher, key_decrypted, std::nullopt ); + BOOST_REQUIRE( decipheredMsg == message ); + } +} + +BOOST_AUTO_TEST_CASE( TEEncryptWithAESAADOnly ) { + // Test encryption with only AES AAD (no TE AAD) + size_t numAll = 4; + size_t numSigned = 3; + + keys keys = generateKeys( numSigned, numAll ); + std::vector< uint8_t > message = randomByteVec( 100 ); + + std::vector< uint8_t > aadAES = { 0xDE, 0xAD, 0xBE, 0xEF }; + + libBLS::EncryptMetaData metaData; + metaData.associatedDataAesGcm = aadAES; // Only AES AAD, no TE AAD + + libBLS::Ciphertext cypher = + libBLS::ThresholdEncryption::encrypt( message, keys.commonPublic, metaData ); + + std::vector< libBLS::TEPublicKeyShare > public_key_shares; + for ( size_t i = 0; i < numAll; i++ ) { + public_key_shares.emplace_back( libBLS::TEPublicKeyShare( keys.secretKeys[i] ) ); + } + + for ( const auto& cipheredKey : cypher.getKeys() ) { + // Validate encryption without TE AAD - should succeed + libBLS::ThresholdEncryption::validateEncryption( cipheredKey, nullptr ); + + libBLS::TEDecryptSet decrSet( numSigned, numAll ); + for ( size_t i = 0; i < numSigned; i++ ) { + libBLS::TEDecryptionShare decr_share = + libBLS::ThresholdEncryption::partialDecrypt( cipheredKey, keys.secretKeys[i] ); + libBLS::ThresholdEncryption::validateDecryptionShare( + cipheredKey, decr_share, public_key_shares[i], nullptr ); + decrSet.addDecryptShare( decr_share ); + } + + libBLS::AES256Key key_decrypted = + libBLS::ThresholdEncryption::combineShares( cipheredKey, decrSet ); + + // Decrypt with AES AAD - should succeed + std::vector< uint8_t > decipheredMsg = + libBLS::ThresholdEncryption::decrypt( cypher, key_decrypted, aadAES ); + BOOST_REQUIRE( decipheredMsg == message ); + } +} + +BOOST_AUTO_TEST_CASE( TEEncryptWithEmptyAAD ) { + size_t numAll = 4; + size_t numSigned = 3; + + keys keys = generateKeys( numSigned, numAll ); + std::vector< uint8_t > message = randomByteVec( 100 ); + + // Empty AAD vectors + std::vector< uint8_t > emptyAadAES = {}; + std::vector< uint8_t > emptyAadTE = {}; + + libBLS::EncryptMetaData metaData; + metaData.associatedDataAesGcm = emptyAadAES; + metaData.associatedDataTE = emptyAadTE; + + libBLS::Ciphertext cypher = + libBLS::ThresholdEncryption::encrypt( message, keys.commonPublic, metaData ); + + std::vector< libBLS::TEPublicKeyShare > public_key_shares; + for ( size_t i = 0; i < numAll; i++ ) { + public_key_shares.emplace_back( libBLS::TEPublicKeyShare( keys.secretKeys[i] ) ); + } + + for ( const auto& cipheredKey : cypher.getKeys() ) { + // Validate with empty TE AAD + libBLS::ThresholdEncryption::validateEncryption( cipheredKey, &emptyAadTE ); + + libBLS::TEDecryptSet decrSet( numSigned, numAll ); + for ( size_t i = 0; i < numSigned; i++ ) { + libBLS::TEDecryptionShare decr_share = + libBLS::ThresholdEncryption::partialDecrypt( cipheredKey, keys.secretKeys[i] ); + decrSet.addDecryptShare( decr_share ); + } + + libBLS::AES256Key key_decrypted = + libBLS::ThresholdEncryption::combineShares( cipheredKey, decrSet ); + + // Decrypt with empty AES AAD + std::vector< uint8_t > decipheredMsg = + libBLS::ThresholdEncryption::decrypt( cypher, key_decrypted, emptyAadAES ); + BOOST_REQUIRE( decipheredMsg == message ); + } +} + +BOOST_AUTO_TEST_CASE( TEValidateWithoutAADOnAADEncrypted ) { + size_t numAll = 4; + size_t numSigned = 3; + + keys keys = generateKeys( numSigned, numAll ); + std::vector< uint8_t > message = randomByteVec( 100 ); + + std::vector< uint8_t > aadTE = { 0x01, 0x02, 0x03, 0x04 }; + + libBLS::EncryptMetaData metaData; + metaData.associatedDataTE = aadTE; + + libBLS::Ciphertext cypher = + libBLS::ThresholdEncryption::encrypt( message, keys.commonPublic, metaData ); + + for ( const auto& cipheredKey : cypher.getKeys() ) { + // Validate without TE AAD (nullptr) - should fail + BOOST_REQUIRE_THROW( + libBLS::ThresholdEncryption::validateEncryption( cipheredKey, nullptr ), + libBLS::ThresholdUtils::IsNotWellFormed ); + + // Validate with correct TE AAD - should succeed + libBLS::ThresholdEncryption::validateEncryption( cipheredKey, &aadTE ); + } +} + +BOOST_AUTO_TEST_CASE( TEBatchValidationWithAAD ) { + // Test batch validation with AAD + size_t numAll = 4; + size_t numSigned = 3; + + keys keys = generateKeys( numSigned, numAll ); + std::vector< uint8_t > message = randomByteVec( 100 ); + + // Create multiple ciphertexts with AAD + size_t batchSize = 5; + std::vector< libBLS::CipheredKey > cipheredKeys; + std::vector< std::vector< uint8_t > > aadVec; + + for ( size_t i = 0; i < batchSize; ++i ) { + std::vector< uint8_t > aadTE = { 0x01, 0x02, 0x03, static_cast< uint8_t >( i ) }; + aadVec.push_back( aadTE ); + + libBLS::EncryptMetaData metaData; + metaData.associatedDataTE = aadTE; + + libBLS::Ciphertext cypher = + libBLS::ThresholdEncryption::encrypt( message, keys.commonPublic, metaData ); + cipheredKeys.push_back( cypher.getKeys()[0] ); + } + + // Batch validate with correct AADs - all should pass + auto results = libBLS::ThresholdEncryption::validateEncryptionBatch( cipheredKeys, &aadVec ); + BOOST_REQUIRE( results.size() == batchSize ); + for ( size_t i = 0; i < batchSize; ++i ) { + BOOST_REQUIRE( results[i] == true ); + } + + // Create wrong AADs + std::vector< std::vector< uint8_t > > wrongAadVec; + for ( size_t i = 0; i < batchSize; ++i ) { + wrongAadVec.push_back( { 0xFF, 0xFE, 0xFD, static_cast< uint8_t >( i ) } ); + } + + // Batch validate with wrong AADs - all should fail + auto wrongResults = + libBLS::ThresholdEncryption::validateEncryptionBatch( cipheredKeys, &wrongAadVec ); + BOOST_REQUIRE( wrongResults.size() == batchSize ); + for ( size_t i = 0; i < batchSize; ++i ) { + BOOST_REQUIRE( wrongResults[i] == false ); + } +} + +BOOST_AUTO_TEST_CASE( TEBatchValidationWithPartialAAD ) { + // Test batch validation with partial AAD (fewer AADs than ciphertexts) + size_t numAll = 4; + size_t numSigned = 3; + + keys keys = generateKeys( numSigned, numAll ); + std::vector< uint8_t > message = randomByteVec( 100 ); + + // Create 5 ciphertexts: first 3 with AAD, last 2 without AAD + size_t batchSize = 5; + size_t aadCount = 3; + std::vector< libBLS::CipheredKey > cipheredKeys; + std::vector< std::vector< uint8_t > > aadVec; + + // First 3 ciphertexts encrypted WITH AAD + for ( size_t i = 0; i < aadCount; ++i ) { + std::vector< uint8_t > aadTE = { 0x01, 0x02, 0x03, static_cast< uint8_t >( i ) }; + aadVec.push_back( aadTE ); + + libBLS::EncryptMetaData metaData; + metaData.associatedDataTE = aadTE; + + libBLS::Ciphertext cypher = + libBLS::ThresholdEncryption::encrypt( message, keys.commonPublic, metaData ); + cipheredKeys.push_back( cypher.getKeys()[0] ); + } + + // Last 2 ciphertexts encrypted WITHOUT AAD + for ( size_t i = aadCount; i < batchSize; ++i ) { + libBLS::Ciphertext cypher = + libBLS::ThresholdEncryption::encrypt( message, keys.commonPublic ); + cipheredKeys.push_back( cypher.getKeys()[0] ); + } + + // Batch validate with partial AAD (3 AADs for 5 ciphertexts) + // First 3 should validate with their AAD, last 2 should validate without AAD + auto results = libBLS::ThresholdEncryption::validateEncryptionBatch( cipheredKeys, &aadVec ); + BOOST_REQUIRE( results.size() == batchSize ); + for ( size_t i = 0; i < batchSize; ++i ) { + BOOST_REQUIRE( results[i] == true ); + } + + // Test parallel version + auto resultsParallel = + libBLS::ThresholdEncryption::validateEncryptionBatchParallel( cipheredKeys, &aadVec ); + BOOST_REQUIRE( resultsParallel.size() == batchSize ); + for ( size_t i = 0; i < batchSize; ++i ) { + BOOST_REQUIRE( resultsParallel[i] == true ); + } + + // Verify that wrong AAD on first 3 fails, while last 2 still pass (no AAD) + std::vector< std::vector< uint8_t > > wrongAadVec; + for ( size_t i = 0; i < aadCount; ++i ) { + wrongAadVec.push_back( { 0xFF, 0xFE, 0xFD, static_cast< uint8_t >( i ) } ); + } + + auto wrongResults = + libBLS::ThresholdEncryption::validateEncryptionBatch( cipheredKeys, &wrongAadVec ); + BOOST_REQUIRE( wrongResults.size() == batchSize ); + // First 3 should fail (wrong AAD) + for ( size_t i = 0; i < aadCount; ++i ) { + BOOST_REQUIRE( wrongResults[i] == false ); + } + // Last 2 should pass (no AAD required) + for ( size_t i = aadCount; i < batchSize; ++i ) { + BOOST_REQUIRE( wrongResults[i] == true ); + } +} + +BOOST_AUTO_TEST_CASE( TEBatchValidationWithEmptyAADEntries ) { + // Test batch validation where some AAD entries are empty vectors + size_t numAll = 4; + size_t numSigned = 3; + + keys keys = generateKeys( numSigned, numAll ); + std::vector< uint8_t > message = randomByteVec( 100 ); + + size_t batchSize = 4; + std::vector< libBLS::CipheredKey > cipheredKeys; + std::vector< std::vector< uint8_t > > aadVec; + + // Create ciphertexts: 0->with AAD, 1->without AAD, 2->with AAD, 3->without AAD + for ( size_t i = 0; i < batchSize; ++i ) { + if ( i % 2 == 0 ) { + // Even indices: encrypt WITH AAD + std::vector< uint8_t > aadTE = { 0xAA, 0xBB, static_cast< uint8_t >( i ) }; + aadVec.push_back( aadTE ); + + libBLS::EncryptMetaData metaData; + metaData.associatedDataTE = aadTE; + + libBLS::Ciphertext cypher = + libBLS::ThresholdEncryption::encrypt( message, keys.commonPublic, metaData ); + cipheredKeys.push_back( cypher.getKeys()[0] ); + } else { + // Odd indices: encrypt WITHOUT AAD and push empty AAD vector + aadVec.push_back( {} ); // empty AAD + + libBLS::Ciphertext cypher = + libBLS::ThresholdEncryption::encrypt( message, keys.commonPublic ); + cipheredKeys.push_back( cypher.getKeys()[0] ); + } + } + + // Batch validate - empty AAD should be treated as no AAD + auto results = libBLS::ThresholdEncryption::validateEncryptionBatch( cipheredKeys, &aadVec ); + BOOST_REQUIRE( results.size() == batchSize ); + for ( size_t i = 0; i < batchSize; ++i ) { + BOOST_REQUIRE( results[i] == true ); + } + + // Test parallel version + auto resultsParallel = + libBLS::ThresholdEncryption::validateEncryptionBatchParallel( cipheredKeys, &aadVec ); + BOOST_REQUIRE( resultsParallel.size() == batchSize ); + for ( size_t i = 0; i < batchSize; ++i ) { + BOOST_REQUIRE( resultsParallel[i] == true ); + } +} + +BOOST_AUTO_TEST_CASE( TEBatchValidationRejectsExcessiveAAD ) { + // Test that batch validation throws when AAD vector is larger than ciphertext vector + size_t numAll = 4; + size_t numSigned = 3; + + keys keys = generateKeys( numSigned, numAll ); + std::vector< uint8_t > message = randomByteVec( 100 ); + + size_t batchSize = 3; + std::vector< libBLS::CipheredKey > cipheredKeys; + std::vector< std::vector< uint8_t > > aadVec; + + // Create 3 ciphertexts + for ( size_t i = 0; i < batchSize; ++i ) { + libBLS::Ciphertext cypher = + libBLS::ThresholdEncryption::encrypt( message, keys.commonPublic ); + cipheredKeys.push_back( cypher.getKeys()[0] ); + } + + // Create 5 AAD entries (more than ciphertexts) + for ( size_t i = 0; i < 5; ++i ) { + aadVec.push_back( { 0x01, 0x02, static_cast< uint8_t >( i ) } ); + } + + // Should throw: AAD size cannot exceed ciphertext size + BOOST_REQUIRE_THROW( + libBLS::ThresholdEncryption::validateEncryptionBatch( cipheredKeys, &aadVec ), + libBLS::ThresholdUtils::IncorrectInput ); + + BOOST_REQUIRE_THROW( + libBLS::ThresholdEncryption::validateEncryptionBatchParallel( cipheredKeys, &aadVec ), + libBLS::ThresholdUtils::IncorrectInput ); +} + +BOOST_AUTO_TEST_CASE( TEDecryptionSharesBatchValidationWithPartialAAD ) { + // Test batch validation of decryption shares with partial AAD + size_t numAll = 4; + size_t numSigned = 3; + + keys keys = generateKeys( numSigned, numAll ); + std::vector< uint8_t > message = randomByteVec( 100 ); + + // Create 4 ciphertexts: first 2 with AAD, last 2 without + size_t batchSize = 4; + size_t aadCount = 2; + std::vector< libBLS::CipheredKey > cipheredKeys; + std::vector< std::vector< uint8_t > > aadVec; + + // First 2 with AAD + for ( size_t i = 0; i < aadCount; ++i ) { + std::vector< uint8_t > aadTE = { 0x10, 0x20, static_cast< uint8_t >( i ) }; + aadVec.push_back( aadTE ); + + libBLS::EncryptMetaData metaData; + metaData.associatedDataTE = aadTE; + + libBLS::Ciphertext cypher = + libBLS::ThresholdEncryption::encrypt( message, keys.commonPublic, metaData ); + cipheredKeys.push_back( cypher.getKeys()[0] ); + } + + // Last 2 without AAD + for ( size_t i = aadCount; i < batchSize; ++i ) { + libBLS::Ciphertext cypher = + libBLS::ThresholdEncryption::encrypt( message, keys.commonPublic ); + cipheredKeys.push_back( cypher.getKeys()[0] ); + } + + // Generate decryption shares for all ciphertexts from all signers + std::vector< libBLS::TEDecryptionShare > shares; + std::vector< libBLS::TEPublicKeyShare > pubKeys; + + for ( size_t i = 0; i < batchSize; ++i ) { + for ( size_t j = 0; j < numAll; ++j ) { + shares.push_back( libBLS::ThresholdEncryption::partialDecrypt( + cipheredKeys[i], keys.secretKeys[j] ) ); + pubKeys.push_back( keys.publicKeys[j] ); + } + } + + // Validate with partial AAD (2 AADs for 4 ciphertexts) + auto results = libBLS::ThresholdEncryption::validateDecryptionSharesBatch( + cipheredKeys, shares, pubKeys, &aadVec ); + BOOST_REQUIRE( results.size() == batchSize * numAll ); + for ( size_t i = 0; i < results.size(); ++i ) { + BOOST_REQUIRE( results[i] == true ); + } + + // Test parallel version + auto resultsParallel = libBLS::ThresholdEncryption::validateDecryptionSharesBatchParallel( + cipheredKeys, shares, pubKeys, &aadVec ); + BOOST_REQUIRE( resultsParallel.size() == batchSize * numAll ); + for ( size_t i = 0; i < resultsParallel.size(); ++i ) { + BOOST_REQUIRE( resultsParallel[i] == true ); + } + + // Test with wrong AAD on first 2 - should fail, last 2 should still pass + std::vector< std::vector< uint8_t > > wrongAadVec = { { 0xFF, 0xFF, 0x00 }, + { 0xFF, 0xFF, 0x01 } }; + + auto wrongResults = libBLS::ThresholdEncryption::validateDecryptionSharesBatch( + cipheredKeys, shares, pubKeys, &wrongAadVec ); + + for ( size_t i = 0; i < batchSize; ++i ) { + for ( size_t j = 0; j < numAll; ++j ) { + size_t idx = i * numAll + j; + if ( i < aadCount ) { + // First 2 ciphertexts: wrong AAD, should fail + BOOST_REQUIRE( wrongResults[idx] == false ); + } else { + // Last 2 ciphertexts: no AAD, should pass + BOOST_REQUIRE( wrongResults[idx] == true ); + } + } + } +} + +BOOST_AUTO_TEST_CASE( TEDecryptionSharesBatchRejectsExcessiveAAD ) { + // Test that decryption share batch validation throws when AAD vector exceeds ciphertext count + size_t numAll = 4; + size_t numSigned = 3; + + keys keys = generateKeys( numSigned, numAll ); + std::vector< uint8_t > message = randomByteVec( 100 ); + + // Create 2 ciphertexts + std::vector< libBLS::CipheredKey > cipheredKeys; + for ( size_t i = 0; i < 2; ++i ) { + libBLS::Ciphertext cypher = + libBLS::ThresholdEncryption::encrypt( message, keys.commonPublic ); + cipheredKeys.push_back( cypher.getKeys()[0] ); + } + + // Generate decryption shares + std::vector< libBLS::TEDecryptionShare > shares; + std::vector< libBLS::TEPublicKeyShare > pubKeys; + for ( size_t i = 0; i < 2; ++i ) { + for ( size_t j = 0; j < numAll; ++j ) { + shares.push_back( libBLS::ThresholdEncryption::partialDecrypt( + cipheredKeys[i], keys.secretKeys[j] ) ); + pubKeys.push_back( keys.publicKeys[j] ); + } + } + + // Create 4 AAD entries (more than 2 ciphertexts) + std::vector< std::vector< uint8_t > > aadVec; + for ( size_t i = 0; i < 4; ++i ) { + aadVec.push_back( { 0x01, static_cast< uint8_t >( i ) } ); + } + + // Should throw: AAD size cannot exceed ciphertext count + BOOST_REQUIRE_THROW( libBLS::ThresholdEncryption::validateDecryptionSharesBatch( + cipheredKeys, shares, pubKeys, &aadVec ), + libBLS::ThresholdUtils::IncorrectInput ); + + BOOST_REQUIRE_THROW( libBLS::ThresholdEncryption::validateDecryptionSharesBatchParallel( + cipheredKeys, shares, pubKeys, &aadVec ), + libBLS::ThresholdUtils::IncorrectInput ); +} + + +BOOST_AUTO_TEST_CASE( TEEncryptDeterministicSeededKeyAndScalar ) { + const size_t numAll = 4; + const size_t numSigned = 3; + + keys keys = generateKeys( numSigned, numAll ); + std::vector< uint8_t > message = randomByteVec( 100 ); + + libBLS::Seed256 seedA{}; + libBLS::Seed256 seedB{}; + seedA.data.fill( 0x42 ); + seedB.data.fill( 0x43 ); + + std::vector< libBLS::algebra::G2Point > rawPublicKeys = { keys.commonPublic.getPublicKeyRaw() }; + + libBLS::EncryptMetaData metaDataA; + metaDataA.seed = seedA; + + libBLS::CipherResult cipher1 = libBLS::TE::encryptWithAES( message, rawPublicKeys, metaDataA ); + libBLS::CipherResult cipher2 = libBLS::TE::encryptWithAES( message, rawPublicKeys, metaDataA ); + + BOOST_REQUIRE( cipher1.ciphertext ); + BOOST_REQUIRE( cipher2.ciphertext ); + BOOST_CHECK( cipher1.randomSecret == cipher2.randomSecret ); + BOOST_CHECK( cipher1.ciphertext->getKeys() == cipher2.ciphertext->getKeys() ); + BOOST_CHECK( cipher1.ciphertext->toBytes() == cipher2.ciphertext->toBytes() ); + + libBLS::EncryptMetaData metaDataB; + metaDataB.seed = seedB; + libBLS::CipherResult cipher3 = libBLS::TE::encryptWithAES( message, rawPublicKeys, metaDataB ); + + BOOST_REQUIRE( cipher3.ciphertext ); + BOOST_CHECK( cipher1.randomSecret != cipher3.randomSecret ); + BOOST_CHECK( cipher1.ciphertext->getKeys() != cipher3.ciphertext->getKeys() ); + BOOST_CHECK( cipher1.ciphertext->toBytes() != cipher3.ciphertext->toBytes() ); + + libBLS::EncryptMetaData metaDataNoSeed; + libBLS::CipherResult cipher4 = + libBLS::TE::encryptWithAES( message, rawPublicKeys, metaDataNoSeed ); + + BOOST_REQUIRE( cipher4.ciphertext ); + BOOST_CHECK( cipher1.ciphertext->getKeys() != cipher4.ciphertext->getKeys() ); + BOOST_CHECK( cipher1.ciphertext->toBytes() != cipher4.ciphertext->toBytes() ); + + std::vector< libBLS::TEPublicKeyShare > publicKeyShares; + publicKeyShares.reserve( numSigned ); + for ( size_t i = 0; i < numSigned; ++i ) { + publicKeyShares.emplace_back( libBLS::TEPublicKeyShare( keys.secretKeys[i] ) ); + } + + const libBLS::CipheredKey& cipheredKey1 = cipher1.ciphertext->getKeys().at( 0 ); + libBLS::ThresholdEncryption::validateEncryption( cipheredKey1, nullptr ); + + libBLS::TEDecryptSet decryptSet1( numSigned, numAll ); + for ( size_t i = 0; i < numSigned; ++i ) { + libBLS::TEDecryptionShare decrShare = + libBLS::ThresholdEncryption::partialDecrypt( cipheredKey1, keys.secretKeys[i] ); + libBLS::ThresholdEncryption::validateDecryptionShare( + cipheredKey1, decrShare, publicKeyShares[i], nullptr ); + decryptSet1.addDecryptShare( decrShare ); + } + + libBLS::AES256Key keyDecrypted1 = + libBLS::ThresholdEncryption::combineShares( cipheredKey1, decryptSet1 ); + std::vector< uint8_t > decryptedMsg1 = + libBLS::ThresholdEncryption::decrypt( *cipher1.ciphertext, keyDecrypted1, std::nullopt ); + BOOST_REQUIRE( decryptedMsg1 == message ); +} + +// Cross-node determinism - two independent "nodes" with same seed produce identical output +BOOST_AUTO_TEST_CASE( TESeededCrossNodeDeterminism ) { + const size_t numAll = 4; + const size_t numSigned = 3; + + keys keys = generateKeys( numSigned, numAll ); + std::vector< uint8_t > message = randomByteVec( 100 ); + + // Fixed seed that both "nodes" will use + libBLS::Seed256 sharedSeed{}; + sharedSeed.data.fill( 0xAB ); + + std::vector< libBLS::algebra::G2Point > rawPublicKeys = { keys.commonPublic.getPublicKeyRaw() }; + + // Simulate Node A encryption + libBLS::EncryptMetaData metaDataNodeA; + metaDataNodeA.seed = sharedSeed; + libBLS::CipherResult cipherNodeA = + libBLS::TE::encryptWithAES( message, rawPublicKeys, metaDataNodeA ); + + // Simulate Node B encryption (independent, same seed) + libBLS::EncryptMetaData metaDataNodeB; + metaDataNodeB.seed = sharedSeed; + libBLS::CipherResult cipherNodeB = + libBLS::TE::encryptWithAES( message, rawPublicKeys, metaDataNodeB ); + + // Both nodes must produce identical ciphertexts + BOOST_REQUIRE( cipherNodeA.ciphertext ); + BOOST_REQUIRE( cipherNodeB.ciphertext ); + BOOST_CHECK( cipherNodeA.randomSecret == cipherNodeB.randomSecret ); + BOOST_CHECK( cipherNodeA.ciphertext->toBytes() == cipherNodeB.ciphertext->toBytes() ); + BOOST_CHECK( cipherNodeA.ciphertext->getKeys() == cipherNodeB.ciphertext->getKeys() ); + BOOST_CHECK( cipherNodeA.ciphertext->getData() == cipherNodeB.ciphertext->getData() ); +} + +// Multiple message sequence - same seed encrypting multiple messages in order +BOOST_AUTO_TEST_CASE( TESeededMultipleMessageSequence ) { + const size_t numAll = 4; + const size_t numSigned = 3; + + keys keys = generateKeys( numSigned, numAll ); + + std::vector< uint8_t > msg1 = randomByteVec( 50 ); + std::vector< uint8_t > msg2 = randomByteVec( 75 ); + std::vector< uint8_t > msg3 = randomByteVec( 100 ); + + libBLS::Seed256 seed{}; + seed.data.fill( 0xCD ); + + std::vector< libBLS::algebra::G2Point > rawPublicKeys = { keys.commonPublic.getPublicKeyRaw() }; + + // Node A encrypts 3 messages in sequence + libBLS::EncryptMetaData metaA1, metaA2, metaA3; + metaA1.seed = seed; + metaA2.seed = seed; + metaA3.seed = seed; + auto cipherA1 = libBLS::TE::encryptWithAES( msg1, rawPublicKeys, metaA1 ); + auto cipherA2 = libBLS::TE::encryptWithAES( msg2, rawPublicKeys, metaA2 ); + auto cipherA3 = libBLS::TE::encryptWithAES( msg3, rawPublicKeys, metaA3 ); + + // Node B encrypts same 3 messages in same sequence + libBLS::EncryptMetaData metaB1, metaB2, metaB3; + metaB1.seed = seed; + metaB2.seed = seed; + metaB3.seed = seed; + auto cipherB1 = libBLS::TE::encryptWithAES( msg1, rawPublicKeys, metaB1 ); + auto cipherB2 = libBLS::TE::encryptWithAES( msg2, rawPublicKeys, metaB2 ); + auto cipherB3 = libBLS::TE::encryptWithAES( msg3, rawPublicKeys, metaB3 ); + + // All corresponding ciphertexts must match between nodes + BOOST_CHECK( cipherA1.ciphertext->toBytes() == cipherB1.ciphertext->toBytes() ); + BOOST_CHECK( cipherA2.ciphertext->toBytes() == cipherB2.ciphertext->toBytes() ); + BOOST_CHECK( cipherA3.ciphertext->toBytes() == cipherB3.ciphertext->toBytes() ); + + // Each ciphertext must be different (different messages) + BOOST_CHECK( cipherA1.ciphertext->toBytes() != cipherA2.ciphertext->toBytes() ); + BOOST_CHECK( cipherA2.ciphertext->toBytes() != cipherA3.ciphertext->toBytes() ); + + // Random secrets are derived from seed, so with same seed they're the SAME + BOOST_CHECK( cipherA1.randomSecret == cipherA2.randomSecret ); + BOOST_CHECK( cipherA2.randomSecret == cipherA3.randomSecret ); + BOOST_CHECK( cipherA1.randomSecret == cipherB1.randomSecret ); +} + +// Seeded ciphertext can be decrypted correctly by any node +BOOST_AUTO_TEST_CASE( TESeededEncryptionDecryptionFlow ) { + const size_t numAll = 4; + const size_t numSigned = 3; + + keys keys = generateKeys( numSigned, numAll ); + std::vector< uint8_t > message = randomByteVec( 100 ); + + libBLS::Seed256 seed{}; + seed.data.fill( 0xEF ); + + // Encrypt with seed using high-level wrapper + libBLS::EncryptMetaData metaData; + metaData.seed = seed; + libBLS::Ciphertext ciphertext = + libBLS::ThresholdEncryption::encrypt( message, keys.commonPublic, metaData ); + + // Prepare public key shares for validation + std::vector< libBLS::TEPublicKeyShare > publicKeyShares; + for ( size_t i = 0; i < numSigned; ++i ) { + publicKeyShares.emplace_back( libBLS::TEPublicKeyShare( keys.secretKeys[i] ) ); + } + + for ( const auto& cipheredKey : ciphertext.getKeys() ) { + // Validate the seeded encryption + libBLS::ThresholdEncryption::validateEncryption( cipheredKey, nullptr ); + + // Partial decrypt with each signer + libBLS::TEDecryptSet decryptSet( numSigned, numAll ); + for ( size_t i = 0; i < numSigned; ++i ) { + libBLS::TEDecryptionShare decrShare = + libBLS::ThresholdEncryption::partialDecrypt( cipheredKey, keys.secretKeys[i] ); + libBLS::ThresholdEncryption::validateDecryptionShare( + cipheredKey, decrShare, publicKeyShares[i], nullptr ); + decryptSet.addDecryptShare( decrShare ); + } + + // Combine shares and decrypt + libBLS::AES256Key aesKey = + libBLS::ThresholdEncryption::combineShares( cipheredKey, decryptSet ); + + // Validate combined decryption + libBLS::ThresholdEncryption::validateCombinedDecryption( + ciphertext, aesKey, keys.commonPublic ); + + // Decrypt and verify message + std::vector< uint8_t > decryptedMessage = + libBLS::ThresholdEncryption::decrypt( ciphertext, aesKey, std::nullopt ); + BOOST_REQUIRE( decryptedMessage == message ); + } +} + BOOST_AUTO_TEST_CASE( TEProcessWithWrappers ) { for ( size_t i = 0; i < 10; i++ ) { size_t numAll = rand_gen() % 16 + 1; diff --git a/test/test_encrypt_message.cpp b/test/test_encrypt_message.cpp index a6f51600..5a69b249 100644 --- a/test/test_encrypt_message.cpp +++ b/test/test_encrypt_message.cpp @@ -63,20 +63,39 @@ BOOST_AUTO_TEST_CASE( EncryptMessage ) { std::string str = libBLS::ThresholdUtils::bytesToHexString( data ); const char* dataStr = str.c_str(); + std::vector< uint8_t > additionalAuthenticatedDataAES = { 'A', 'E', 'S' }; + std::vector< uint8_t > additionalAuthenticatedDataTE = { 'T', 'E' }; + std::string additionalAuthenticatedDataAESStr = + libBLS::ThresholdUtils::bytesToHexString( additionalAuthenticatedDataAES ); + const char* additionalAuthenticatedDataAESStrC = additionalAuthenticatedDataAESStr.c_str(); + + std::string additionalAuthenticatedDataTEStr = + libBLS::ThresholdUtils::bytesToHexString( additionalAuthenticatedDataTE ); + const char* additionalAuthenticatedDataTEStrC = additionalAuthenticatedDataTEStr.c_str(); + // call encrypt message - const char* cipheredMessage = encryptMessage( dataStr, pKeyStr.c_str() ); + const char* cipheredMessage = encryptMessage( dataStr, pKeyStr.c_str(), + additionalAuthenticatedDataTEStrC, additionalAuthenticatedDataAESStrC ); std::vector< uint8_t > cipheredMessageBytesActual = libBLS::ThresholdUtils::hexCStringToBytes( cipheredMessage ); // encrypt message using libBLS libBLS::TEPublicKey publicKey( pKeyStr, libBLS::Base::HEXA ); - libBLS::Ciphertext ciphertext = libBLS::ThresholdEncryption::encrypt( data, publicKey ); + libBLS::EncryptMetaData metaData; + metaData.associatedDataAesGcm = additionalAuthenticatedDataAES; + metaData.associatedDataTE = additionalAuthenticatedDataTE; + + libBLS::Ciphertext ciphertext = + libBLS::ThresholdEncryption::encrypt( data, publicKey, metaData ); std::vector< uint8_t > cipheredMessageBytesTarget = ciphertext.toBytes(); // cannot compare their contents since each has a different random secret, which results // in different ciphered messages - just check their lengths BOOST_REQUIRE( cipheredMessageBytesActual.size() == cipheredMessageBytesTarget.size() ); + libBLS::ThresholdEncryption::validateEncryption( + ciphertext.keys[0], &additionalAuthenticatedDataTE ); + // run TE process over the ciphered message from JS libBLS::Ciphertext cipheredMessageObj = libBLS::Ciphertext::fromBytes( cipheredMessageBytesActual ); @@ -92,10 +111,11 @@ BOOST_AUTO_TEST_CASE( EncryptMessage ) { libBLS::AES256Key key_deciphered = libBLS::ThresholdEncryption::combineShares( cipheredKey, decr_set ); - libBLS::ThresholdEncryption::validateCombinedDecryption( - cipheredMessageObj, key_deciphered, keys.commonPublic.getPublicKeyRaw() ); - std::vector< uint8_t > decipheredMsg = - libBLS::ThresholdEncryption::decrypt( cipheredMessageObj, key_deciphered ); + libBLS::ThresholdEncryption::validateCombinedDecryption( cipheredMessageObj, + key_deciphered, keys.commonPublic.getPublicKeyRaw(), + additionalAuthenticatedDataAES ); + std::vector< uint8_t > decipheredMsg = libBLS::ThresholdEncryption::decrypt( + cipheredMessageObj, key_deciphered, additionalAuthenticatedDataAES ); BOOST_REQUIRE( decipheredMsg == data ); } @@ -125,9 +145,19 @@ BOOST_AUTO_TEST_CASE( EncryptMessage ) { publicKeysStr[j] = pKeyStr; } + std::vector< uint8_t > additionalAuthenticatedDataAES = { 'A', 'E', 'S' }; + std::vector< uint8_t > additionalAuthenticatedDataTE = { 'T', 'E' }; + std::string additionalAuthenticatedDataAESStr = + libBLS::ThresholdUtils::bytesToHexString( additionalAuthenticatedDataAES ); + const char* additionalAuthenticatedDataAESStrC = additionalAuthenticatedDataAESStr.c_str(); + std::string additionalAuthenticatedDataTEStr = + libBLS::ThresholdUtils::bytesToHexString( additionalAuthenticatedDataTE ); + const char* additionalAuthenticatedDataTEStrC = additionalAuthenticatedDataTEStr.c_str(); + // call encrypt message const char* cipheredMessage = - encryptMessageDualKey( dataStr, publicKeysStr[0].c_str(), publicKeysStr[1].c_str() ); + encryptMessageDualKey( dataStr, publicKeysStr[0].c_str(), publicKeysStr[1].c_str(), + additionalAuthenticatedDataTEStrC, additionalAuthenticatedDataAESStrC ); std::vector< uint8_t > cipheredMessageBytesActual = libBLS::ThresholdUtils::hexCStringToBytes( cipheredMessage ); @@ -136,8 +166,11 @@ BOOST_AUTO_TEST_CASE( EncryptMessage ) { for ( const auto& publicKey : publicKeysStr ) { commonPublicKeys.push_back( libBLS::TEPublicKey( publicKey, libBLS::Base::HEXA ) ); } + libBLS::EncryptMetaData metaData; + metaData.associatedDataAesGcm = additionalAuthenticatedDataAES; + metaData.associatedDataTE = additionalAuthenticatedDataTE; libBLS::Ciphertext ciphertext = - libBLS::ThresholdEncryption::encrypt( data, commonPublicKeys ); + libBLS::ThresholdEncryption::encrypt( data, commonPublicKeys, metaData ); std::vector< uint8_t > cipheredMessageBytesTarget = ciphertext.toBytes(); // cannot compare their contents since each has a different random secret, which results @@ -162,20 +195,23 @@ BOOST_AUTO_TEST_CASE( EncryptMessage ) { libBLS::ThresholdEncryption::combineShares( cipheredKeys[k], decr_set ); libBLS::Ciphertext tempCipheredMessage( cipheredMessageObj.getKeys()[k], cipheredMessageObj.getData() ); - libBLS::ThresholdEncryption::validateCombinedDecryption( - tempCipheredMessage, key_deciphered, keys[k].commonPublic.getPublicKeyRaw() ); - std::vector< uint8_t > decipheredMsg = - libBLS::ThresholdEncryption::decrypt( tempCipheredMessage, key_deciphered ); + libBLS::ThresholdEncryption::validateCombinedDecryption( tempCipheredMessage, + key_deciphered, keys[k].commonPublic.getPublicKeyRaw(), + additionalAuthenticatedDataAES ); + std::vector< uint8_t > decipheredMsg = libBLS::ThresholdEncryption::decrypt( + tempCipheredMessage, key_deciphered, additionalAuthenticatedDataAES ); BOOST_REQUIRE( decipheredMsg == data ); auto ciphertextCopy = cipheredMessageObj; - BOOST_REQUIRE_THROW( libBLS::ThresholdEncryption::validateAndDecrypt( - ciphertextCopy, key_deciphered, keys[k].commonPublic ), + BOOST_REQUIRE_THROW( + libBLS::ThresholdEncryption::validateAndDecrypt( ciphertextCopy, key_deciphered, + keys[k].commonPublic, additionalAuthenticatedDataAES ), libBLS::ThresholdUtils::IncorrectInput ); ciphertextCopy.keepKey( k ); - BOOST_REQUIRE( libBLS::ThresholdEncryption::validateAndDecrypt( - ciphertextCopy, key_deciphered, keys[k].commonPublic ) == data ); + BOOST_REQUIRE( + libBLS::ThresholdEncryption::validateAndDecrypt( ciphertextCopy, key_deciphered, + keys[k].commonPublic, additionalAuthenticatedDataAES ) == data ); } } } diff --git a/test/unit_tests_te.cpp b/test/unit_tests_te.cpp index c195f2f3..cb4d4df7 100644 --- a/test/unit_tests_te.cpp +++ b/test/unit_tests_te.cpp @@ -21,12 +21,14 @@ @date 2019 */ +#include +#include #include #include "test/utils.h" +#include "threshold_encryption/AesGcmCipher.h" #include #include -#include #include @@ -41,67 +43,446 @@ BOOST_TEST_GLOBAL_CONFIGURATION( GlobalConfig ); BOOST_AUTO_TEST_SUITE( TestAES ) +// Test the default constructor generates a random key +BOOST_AUTO_TEST_CASE( RandomKeyConstructor ) { + libBLS::ThresholdUtils::initRAND(); + + const std::string message = "Test message for random key constructor"; + std::vector< uint8_t > messageBytes( message.begin(), message.end() ); + + // Create cipher with random key + libBLS::AesGcmCipher cipher; + + // Encrypt and decrypt + auto ciphertext = cipher.encrypt( messageBytes ); + auto decrypted = cipher.decrypt( ciphertext ); + + BOOST_REQUIRE( decrypted == messageBytes ); + + // Verify getKey() returns a non-zero key + const auto& key = cipher.getKey(); + bool allZeros = std::all_of( key.begin(), key.end(), []( uint8_t b ) { return b == 0; } ); + BOOST_REQUIRE( !allZeros ); +} + +// Test that two random ciphers produce different keys +BOOST_AUTO_TEST_CASE( RandomKeyUniqueness ) { + libBLS::ThresholdUtils::initRAND(); + + libBLS::AesGcmCipher cipher1; + libBLS::AesGcmCipher cipher2; + + // Two separate random ciphers should have different keys + BOOST_REQUIRE( cipher1.getKey() != cipher2.getKey() ); +} + +// Test seeded constructor produces deterministic key +BOOST_AUTO_TEST_CASE( SeededKeyDeterminism ) { + libBLS::ThresholdUtils::initRAND(); + + // Create a fixed seed + libBLS::Seed256 seed; + RAND_bytes( seed.data.data(), seed.data.size() ); + + // Create two ciphers with the same seed + libBLS::AesGcmCipher cipher1{ seed }; + libBLS::AesGcmCipher cipher2{ seed }; + + // Both should produce the same key + BOOST_REQUIRE( cipher1.getKey() == cipher2.getKey() ); +} + +// Test seeded constructor - same seed produces same ciphertext for same plaintext sequence +BOOST_AUTO_TEST_CASE( SeededEncryptionDeterminism ) { + libBLS::ThresholdUtils::initRAND(); + + // Create a fixed seed + libBLS::Seed256 seed; + RAND_bytes( seed.data.data(), seed.data.size() ); + + const std::string message1 = "First message"; + const std::string message2 = "Second message"; + std::vector< uint8_t > msg1Bytes( message1.begin(), message1.end() ); + std::vector< uint8_t > msg2Bytes( message2.begin(), message2.end() ); + + // Simulate two nodes with same seed + libBLS::AesGcmCipher node1Cipher{ seed }; + libBLS::AesGcmCipher node2Cipher{ seed }; + + // Encrypt same messages in same order + auto ct1_node1 = node1Cipher.encrypt( msg1Bytes ); + auto ct2_node1 = node1Cipher.encrypt( msg2Bytes ); + + auto ct1_node2 = node2Cipher.encrypt( msg1Bytes ); + auto ct2_node2 = node2Cipher.encrypt( msg2Bytes ); + + // Both nodes should produce identical ciphertexts + BOOST_REQUIRE( ct1_node1 == ct1_node2 ); + BOOST_REQUIRE( ct2_node1 == ct2_node2 ); + + // Verify same message encrypted again produces DIFFERENT ciphertext (counter incremented) + auto ct3_node1 = node1Cipher.encrypt( msg1Bytes ); + BOOST_REQUIRE( ct1_node1 != ct3_node1 ); +} + +// Test that different seeds produce different keys +BOOST_AUTO_TEST_CASE( DifferentSeedsDifferentKeys ) { + libBLS::ThresholdUtils::initRAND(); + + libBLS::Seed256 seed1; + libBLS::Seed256 seed2; + RAND_bytes( seed1.data.data(), seed1.data.size() ); + RAND_bytes( seed2.data.data(), seed2.data.size() ); + + libBLS::AesGcmCipher cipher1{ seed1 }; + libBLS::AesGcmCipher cipher2{ seed2 }; + + BOOST_REQUIRE( cipher1.getKey() != cipher2.getKey() ); +} + +// Test raw key constructor +BOOST_AUTO_TEST_CASE( RawKeyConstructor ) { + libBLS::ThresholdUtils::initRAND(); + + // Generate a key manually + libBLS::AES256Key rawKey; + RAND_bytes( rawKey.data(), rawKey.size() ); + + // Create cipher with raw key + libBLS::AesGcmCipher cipher{ rawKey }; + + // Verify getKey() returns the same key + BOOST_REQUIRE( cipher.getKey() == rawKey ); +} + +// Test raw key constructor - encrypt/decrypt round trip +BOOST_AUTO_TEST_CASE( RawKeyRoundTrip ) { + libBLS::ThresholdUtils::initRAND(); + + libBLS::AES256Key rawKey; + RAND_bytes( rawKey.data(), rawKey.size() ); + + const std::string message = "Test message for raw key round trip"; + std::vector< uint8_t > messageBytes( message.begin(), message.end() ); + + // Encrypt with one instance + libBLS::AesGcmCipher encryptor{ rawKey }; + auto ciphertext = encryptor.encrypt( messageBytes ); + + // Decrypt with a new instance using same key + libBLS::AesGcmCipher decryptor{ rawKey }; + auto decrypted = decryptor.decrypt( ciphertext ); + + BOOST_REQUIRE( decrypted == messageBytes ); +} + BOOST_AUTO_TEST_CASE( SimpleAES ) { libBLS::ThresholdUtils::initRAND(); - unsigned char key_bytes[32]; - RAND_bytes( key_bytes, sizeof( key_bytes ) ); - libBLS::AES256Key random_aes_key; - std::copy( key_bytes, key_bytes + libBLS::AES_256_KEY_SIZE_BYTES, random_aes_key.begin() ); + unsigned char keyBytes[32]; + RAND_bytes( keyBytes, sizeof( keyBytes ) ); + libBLS::AES256Key randomAesKey; + std::copy( keyBytes, keyBytes + libBLS::AES_256_KEY_SIZE_BYTES, randomAesKey.begin() ); const std::string message = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; - std::vector< uint8_t > message_bytes( message.begin(), message.end() ); + std::vector< uint8_t > messageBytes( message.begin(), message.end() ); - libBLS::AesGcmCipher cipher{ random_aes_key }; - auto ciphertext = cipher.encrypt( message_bytes ); - auto decrypted_text = cipher.decrypt( ciphertext ); + libBLS::AesGcmCipher cipher{ randomAesKey }; + auto ciphertext = cipher.encrypt( messageBytes ); + auto decryptedText = cipher.decrypt( ciphertext ); - BOOST_REQUIRE( decrypted_text == message_bytes ); + BOOST_REQUIRE( decryptedText == messageBytes ); } BOOST_AUTO_TEST_CASE( wrongCiphertext ) { libBLS::ThresholdUtils::initRAND(); - libBLS::AES256Key random_aes_key; - RAND_bytes( random_aes_key.data(), random_aes_key.size() ); + libBLS::AES256Key randomAesKey; + RAND_bytes( randomAesKey.data(), randomAesKey.size() ); const std::string message = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; - std::vector< uint8_t > message_bytes( message.begin(), message.end() ); + std::vector< uint8_t > messageBytes( message.begin(), message.end() ); - const std::string bad_message = + const std::string badMessage = "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"; - std::vector< uint8_t > bad_message_bytes( bad_message.begin(), bad_message.end() ); + std::vector< uint8_t > badMessageBytes( badMessage.begin(), badMessage.end() ); - libBLS::AesGcmCipher cipher{ random_aes_key }; - auto bad_ciphertext = cipher.encrypt( bad_message_bytes ); + libBLS::AesGcmCipher cipher{ randomAesKey }; + auto bad_ciphertext = cipher.encrypt( badMessageBytes ); - auto decrypted_text = cipher.decrypt( bad_ciphertext ); + auto decryptedText = cipher.decrypt( bad_ciphertext ); - BOOST_REQUIRE( decrypted_text != message_bytes ); - BOOST_REQUIRE( decrypted_text == bad_message_bytes ); + BOOST_REQUIRE( decryptedText != messageBytes ); + BOOST_REQUIRE( decryptedText == badMessageBytes ); } BOOST_AUTO_TEST_CASE( wrongKey ) { libBLS::ThresholdUtils::initRAND(); - unsigned char key_bytes[32]; - RAND_bytes( key_bytes, sizeof( key_bytes ) ); - libBLS::AES256Key random_aes_key; - std::copy( key_bytes, key_bytes + libBLS::AES_256_KEY_SIZE_BYTES, random_aes_key.begin() ); + unsigned char keyBytes[32]; + RAND_bytes( keyBytes, sizeof( keyBytes ) ); + libBLS::AES256Key randomAesKey; + std::copy( keyBytes, keyBytes + libBLS::AES_256_KEY_SIZE_BYTES, randomAesKey.begin() ); const std::string message = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; - std::vector< uint8_t > message_bytes( message.begin(), message.end() ); + std::vector< uint8_t > messageBytes( message.begin(), message.end() ); - libBLS::AesGcmCipher cipher{ random_aes_key }; - auto ciphertext = cipher.encrypt( message_bytes ); + libBLS::AesGcmCipher cipher{ randomAesKey }; + auto ciphertext = cipher.encrypt( messageBytes ); - unsigned char bad_key_bytes[32]; - RAND_bytes( bad_key_bytes, sizeof( bad_key_bytes ) ); - libBLS::AES256Key random_bad_aes_key; + unsigned char bad_keyBytes[32]; + RAND_bytes( bad_keyBytes, sizeof( bad_keyBytes ) ); + libBLS::AES256Key randomBadAesKey; std::copy( - bad_key_bytes, bad_key_bytes + libBLS::AES_256_KEY_SIZE_BYTES, random_bad_aes_key.begin() ); + bad_keyBytes, bad_keyBytes + libBLS::AES_256_KEY_SIZE_BYTES, randomBadAesKey.begin() ); - libBLS::AesGcmCipher bad_cipher{ random_bad_aes_key }; + libBLS::AesGcmCipher bad_cipher{ randomBadAesKey }; BOOST_REQUIRE_THROW( bad_cipher.decrypt( ciphertext ), std::runtime_error ); } +BOOST_AUTO_TEST_CASE( AESWithAAD ) { + libBLS::ThresholdUtils::initRAND(); + libBLS::AES256Key randomAesKey; + RAND_bytes( randomAesKey.data(), randomAesKey.size() ); + + const std::string message = "Hello, this is a test message for AAD encryption!"; + std::vector< uint8_t > messageBytes( message.begin(), message.end() ); + + // Create AAD (additional authenticated data) + std::vector< uint8_t > aad = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08 }; + + libBLS::AesGcmCipher cipher{ randomAesKey }; + + // Encrypt with AAD + auto ciphertext = cipher.encrypt( messageBytes, aad ); + + // Decrypt with same AAD - should succeed + auto decryptedText = cipher.decrypt( ciphertext, aad ); + BOOST_REQUIRE( decryptedText == messageBytes ); +} + +BOOST_AUTO_TEST_CASE( AESWithWrongAAD ) { + libBLS::ThresholdUtils::initRAND(); + libBLS::AES256Key randomAesKey; + RAND_bytes( randomAesKey.data(), randomAesKey.size() ); + + const std::string message = "Hello, this is a test message for AAD encryption!"; + std::vector< uint8_t > messageBytes( message.begin(), message.end() ); + + // Create AAD + std::vector< uint8_t > aad = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08 }; + // Different AAD + std::vector< uint8_t > wrong_aad = { 0xFF, 0xFE, 0xFD, 0xFC }; + + libBLS::AesGcmCipher cipher{ randomAesKey }; + + // Encrypt with AAD + auto ciphertext = cipher.encrypt( messageBytes, aad ); + + // Decrypt with different AAD - should fail (authentication error) + BOOST_REQUIRE_THROW( cipher.decrypt( ciphertext, wrong_aad ), std::runtime_error ); +} + +BOOST_AUTO_TEST_CASE( AESWithMissingAAD ) { + libBLS::ThresholdUtils::initRAND(); + libBLS::AES256Key randomAesKey; + RAND_bytes( randomAesKey.data(), randomAesKey.size() ); + + const std::string message = "Hello, this is a test message for AAD encryption!"; + std::vector< uint8_t > messageBytes( message.begin(), message.end() ); + + // Create AAD + std::vector< uint8_t > aad = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08 }; + + libBLS::AesGcmCipher cipher{ randomAesKey }; + + // Encrypt with AAD + auto ciphertext = cipher.encrypt( messageBytes, aad ); + + // Decrypt without AAD (nullopt) - should fail (authentication error) + BOOST_REQUIRE_THROW( cipher.decrypt( ciphertext, std::nullopt ), std::runtime_error ); +} + +BOOST_AUTO_TEST_CASE( AESWithoutAAD_BackwardCompatibility ) { + libBLS::ThresholdUtils::initRAND(); + libBLS::AES256Key randomAesKey; + RAND_bytes( randomAesKey.data(), randomAesKey.size() ); + + const std::string message = "Hello, this is a test message without AAD!"; + std::vector< uint8_t > messageBytes( message.begin(), message.end() ); + + libBLS::AesGcmCipher cipher{ randomAesKey }; + + // Encrypt without AAD (backward compatible) + auto ciphertext = cipher.encrypt( messageBytes ); + + // Decrypt without AAD - should succeed + auto decryptedText = cipher.decrypt( ciphertext ); + BOOST_REQUIRE( decryptedText == messageBytes ); + + // Encrypt without AAD, try to decrypt with AAD - should fail + auto ciphertext2 = cipher.encrypt( messageBytes, std::nullopt ); + std::vector< uint8_t > fakeAad = { 0x01, 0x02, 0x03 }; + BOOST_REQUIRE_THROW( cipher.decrypt( ciphertext2, fakeAad ), std::runtime_error ); +} + +BOOST_AUTO_TEST_CASE( AESWithEmptyAAD ) { + libBLS::ThresholdUtils::initRAND(); + libBLS::AES256Key randomAesKey; + RAND_bytes( randomAesKey.data(), randomAesKey.size() ); + + const std::string message = "Hello, this is a test message with empty AAD!"; + std::vector< uint8_t > messageBytes( message.begin(), message.end() ); + + // Empty AAD (different from nullopt) + std::vector< uint8_t > empty_aad = {}; + + libBLS::AesGcmCipher cipher{ randomAesKey }; + + // Encrypt with empty AAD + auto ciphertext = cipher.encrypt( messageBytes, empty_aad ); + + // Decrypt with empty AAD - should succeed + auto decryptedText = cipher.decrypt( ciphertext, empty_aad ); + BOOST_REQUIRE( decryptedText == messageBytes ); + + // Empty AAD should behave the same as nullopt + auto decryptedText2 = cipher.decrypt( ciphertext, std::nullopt ); + BOOST_REQUIRE( decryptedText2 == messageBytes ); +} + +BOOST_AUTO_TEST_CASE( AESWithTamperedCiphertext ) { + libBLS::ThresholdUtils::initRAND(); + libBLS::AES256Key randomAesKey; + RAND_bytes( randomAesKey.data(), randomAesKey.size() ); + + const std::string message = "Test message for tampered ciphertext!"; + std::vector< uint8_t > messageBytes( message.begin(), message.end() ); + std::vector< uint8_t > aad = { 0x01, 0x02, 0x03, 0x04 }; + + libBLS::AesGcmCipher cipher{ randomAesKey }; + + // Encrypt with AAD + auto ciphertext = cipher.encrypt( messageBytes, aad ); + + // Tamper with the ciphertext data (not the IV or tag) + if ( ciphertext.size() > libBLS::AES_GCM_IV_SIZE + libBLS::AES_GCM_TAG_SIZE + 1 ) { + // ciphertext is between IV (start) and tag (end) + ciphertext[libBLS::AES_GCM_IV_SIZE + 5] ^= 0xFF; + + // Decryption should fail due to authentication tag mismatch + BOOST_REQUIRE_THROW( cipher.decrypt( ciphertext, aad ), std::runtime_error ); + } +} + +BOOST_AUTO_TEST_CASE( AESWithTamperedTag ) { + libBLS::ThresholdUtils::initRAND(); + libBLS::AES256Key randomAesKey; + RAND_bytes( randomAesKey.data(), randomAesKey.size() ); + + const std::string message = "Test message for tampered tag!"; + std::vector< uint8_t > messageBytes( message.begin(), message.end() ); + std::vector< uint8_t > aad = { 0xAA, 0xBB, 0xCC }; + + libBLS::AesGcmCipher cipher{ randomAesKey }; + + // Encrypt with AAD + auto ciphertext = cipher.encrypt( messageBytes, aad ); + + // Tamper with the authentication tag (last 16 bytes) + if ( ciphertext.size() >= libBLS::AES_GCM_TAG_SIZE ) { + // tag is at the end of the ciphertext + size_t tagStart = ciphertext.size() - libBLS::AES_GCM_TAG_SIZE; + ciphertext[tagStart] ^= 0x01; + + // Decryption should fail due to tag mismatch + BOOST_REQUIRE_THROW( cipher.decrypt( ciphertext, aad ), std::runtime_error ); + } +} + +BOOST_AUTO_TEST_CASE( AESWithTamperedIV ) { + libBLS::ThresholdUtils::initRAND(); + libBLS::AES256Key randomAesKey; + RAND_bytes( randomAesKey.data(), randomAesKey.size() ); + + const std::string message = "Test message for tampered IV!"; + std::vector< uint8_t > messageBytes( message.begin(), message.end() ); + std::vector< uint8_t > aad = { 0x11, 0x22, 0x33, 0x44 }; + + libBLS::AesGcmCipher cipher{ randomAesKey }; + + // Encrypt with AAD + auto ciphertext = cipher.encrypt( messageBytes, aad ); + + // Tamper with the IV (first 12 bytes) + if ( ciphertext.size() >= libBLS::AES_GCM_IV_SIZE ) { + // IV is at the start + ciphertext[5] ^= 0xAA; + + // Decryption should fail - either due to wrong decryption or tag mismatch + BOOST_REQUIRE_THROW( cipher.decrypt( ciphertext, aad ), std::runtime_error ); + } +} + +BOOST_AUTO_TEST_CASE( AESAADLargePayload ) { + libBLS::ThresholdUtils::initRAND(); + libBLS::AES256Key randomAesKey; + RAND_bytes( randomAesKey.data(), randomAesKey.size() ); + + // Large message (1 MB) + std::vector< uint8_t > largeMessage( 1024 * 1024 ); + RAND_bytes( largeMessage.data(), largeMessage.size() ); + + // Large AAD (64 KB) + std::vector< uint8_t > largeAad( 64 * 1024 ); + RAND_bytes( largeAad.data(), largeAad.size() ); + + libBLS::AesGcmCipher cipher{ randomAesKey }; + + // Encrypt with large AAD + auto ciphertext = cipher.encrypt( largeMessage, largeAad ); + + // Decrypt with same large AAD - should succeed + auto decrypted = cipher.decrypt( ciphertext, largeAad ); + BOOST_REQUIRE( decrypted == largeMessage ); + + // Modify one byte in the large AAD - should fail + largeAad[1234] ^= 0x01; + BOOST_REQUIRE_THROW( cipher.decrypt( ciphertext, largeAad ), std::runtime_error ); +} + +BOOST_AUTO_TEST_CASE( AESMultipleEncryptionsWithDifferentAAD ) { + libBLS::ThresholdUtils::initRAND(); + libBLS::AES256Key randomAesKey; + RAND_bytes( randomAesKey.data(), randomAesKey.size() ); + + libBLS::AesGcmCipher cipher{ randomAesKey }; + + const std::string msg1 = "First message"; + const std::string msg2 = "Second message"; + const std::string msg3 = "Third message"; + + std::vector< uint8_t > msg1Bytes( msg1.begin(), msg1.end() ); + std::vector< uint8_t > msg2Bytes( msg2.begin(), msg2.end() ); + std::vector< uint8_t > msg3Bytes( msg3.begin(), msg3.end() ); + + std::vector< uint8_t > aad1 = { 0x01 }; + std::vector< uint8_t > aad2 = { 0x02 }; + std::vector< uint8_t > aad3 = { 0x03 }; + + // Encrypt three messages with different AADs + auto ct1 = cipher.encrypt( msg1Bytes, aad1 ); + auto ct2 = cipher.encrypt( msg2Bytes, aad2 ); + auto ct3 = cipher.encrypt( msg3Bytes, aad3 ); + + // Decrypt each with correct AAD + BOOST_REQUIRE( cipher.decrypt( ct1, aad1 ) == msg1Bytes ); + BOOST_REQUIRE( cipher.decrypt( ct2, aad2 ) == msg2Bytes ); + BOOST_REQUIRE( cipher.decrypt( ct3, aad3 ) == msg3Bytes ); + + // Cross-decryption with wrong AAD should fail + BOOST_REQUIRE_THROW( cipher.decrypt( ct1, aad2 ), std::runtime_error ); + BOOST_REQUIRE_THROW( cipher.decrypt( ct2, aad3 ), std::runtime_error ); + BOOST_REQUIRE_THROW( cipher.decrypt( ct3, aad1 ), std::runtime_error ); +} + BOOST_AUTO_TEST_SUITE_END() @@ -111,43 +492,43 @@ BOOST_AUTO_TEST_CASE( CipheredKey ) { for ( size_t i = 0; i < 20; i++ ) { // random key data libBLS::algebra::G2Point u = libBLS::algebra::G2Point::random(); - libBLS::AES256Key ciphered_key; - RAND_bytes( ciphered_key.data(), ciphered_key.size() ); + libBLS::AES256Key cipheredKey; + RAND_bytes( cipheredKey.data(), cipheredKey.size() ); libBLS::algebra::G1Point w = libBLS::algebra::G1Point::random(); // check constructor - libBLS::CipheredKey key = libBLS::CipheredKey( u, ciphered_key, w ); + libBLS::CipheredKey key = libBLS::CipheredKey( u, cipheredKey, w ); BOOST_REQUIRE( key.U == u ); - BOOST_REQUIRE( key.V == ciphered_key ); + BOOST_REQUIRE( key.V == cipheredKey ); BOOST_REQUIRE( key.W == w ); // convert to bytes & back std::array< uint8_t, libBLS::CipheredKey::CIPHERED_KEY_SIZE_BYTES > bytes = key.toBytes(); - libBLS::CipheredKey restored_key = libBLS::CipheredKey::fromBytes( bytes ); + libBLS::CipheredKey restoredKey = libBLS::CipheredKey::fromBytes( bytes ); - BOOST_REQUIRE( key == restored_key ); + BOOST_REQUIRE( key == restoredKey ); } } BOOST_AUTO_TEST_CASE( CipheredKeyException ) { // zero u element libBLS::algebra::G2Point u = libBLS::algebra::G2Point::identity(); - libBLS::AES256Key ciphered_key; - RAND_bytes( ciphered_key.data(), ciphered_key.size() ); + libBLS::AES256Key cipheredKey; + RAND_bytes( cipheredKey.data(), cipheredKey.size() ); libBLS::algebra::G1Point w = libBLS::algebra::G1Point::random(); BOOST_REQUIRE_THROW( - libBLS::CipheredKey( u, ciphered_key, w ), libBLS::ThresholdUtils::IsNotWellFormed ); + libBLS::CipheredKey( u, cipheredKey, w ), libBLS::ThresholdUtils::IsNotWellFormed ); // zero w element u = libBLS::algebra::G2Point::random(); w = libBLS::algebra::G1Point::identity(); BOOST_REQUIRE_THROW( - libBLS::CipheredKey( u, ciphered_key, w ), libBLS::ThresholdUtils::IsNotWellFormed ); + libBLS::CipheredKey( u, cipheredKey, w ), libBLS::ThresholdUtils::IsNotWellFormed ); // correct ciphered key, but changed U mid-execution w = libBLS::algebra::G1Point::random(); - libBLS::CipheredKey key = libBLS::CipheredKey( u, ciphered_key, w ); + libBLS::CipheredKey key = libBLS::CipheredKey( u, cipheredKey, w ); key.U = libBLS::algebra::G2Point::identity(); BOOST_REQUIRE_THROW( key.validate(), libBLS::ThresholdUtils::IsNotWellFormed ); @@ -161,11 +542,11 @@ BOOST_AUTO_TEST_CASE( Ciphertext ) { for ( size_t i = 0; i < 20; i++ ) { // random key data libBLS::algebra::G2Point u = libBLS::algebra::G2Point::random(); - libBLS::AES256Key ciphered_key; - RAND_bytes( ciphered_key.data(), ciphered_key.size() ); + libBLS::AES256Key cipheredKey; + RAND_bytes( cipheredKey.data(), cipheredKey.size() ); libBLS::algebra::G1Point w = libBLS::algebra::G1Point::random(); // convert to bytes & back - libBLS::CipheredKey key = libBLS::CipheredKey( u, ciphered_key, w ); + libBLS::CipheredKey key = libBLS::CipheredKey( u, cipheredKey, w ); // random 1000 bytes std::vector< uint8_t > data; @@ -245,40 +626,40 @@ BOOST_AUTO_TEST_CASE( CiphertextException ) { BOOST_AUTO_TEST_CASE( SimpleEncryption ) { libBLS::TE te_instance = libBLS::TE( 1, 1 ); - libBLS::AES256Key random_aes_key; - RAND_bytes( random_aes_key.data(), random_aes_key.size() ); + libBLS::AES256Key randomAesKey; + RAND_bytes( randomAesKey.data(), randomAesKey.size() ); - libBLS::algebra::FrScalar secret_key = libBLS::algebra::FrScalar::random(); + libBLS::algebra::FrScalar secretKey = libBLS::algebra::FrScalar::random(); - libBLS::algebra::G2Point public_key = secret_key * libBLS::algebra::G2Point::generator(); + libBLS::algebra::G2Point publicKey = secretKey * libBLS::algebra::G2Point::generator(); - auto result = te_instance.getCiphertext( random_aes_key, public_key ); + auto result = te_instance.getCiphertext( randomAesKey, publicKey, std::nullopt, std::nullopt ); // one decrypt share at a time for ( const auto& cipheredKey : result.ciphertext ) { std::vector< libBLS::algebra::G2Point > shares1; - libBLS::algebra::G2Point decryption_share = - te_instance.getDecryptionShare( cipheredKey, secret_key ); - shares1.push_back( decryption_share ); + libBLS::algebra::G2Point decryptionShare = + te_instance.getDecryptionShare( cipheredKey, secretKey ); + shares1.push_back( decryptionShare ); // standalone validation - BOOST_REQUIRE( te_instance.Verify( cipheredKey, decryption_share, public_key ) ); + BOOST_REQUIRE( te_instance.Verify( cipheredKey, decryptionShare, publicKey ) ); // batched decryption - optimistic std::vector< libBLS::CipheredKey > cipheredKeys = { cipheredKey }; - std::vector< bool > verifications_key1 = - te_instance.VerifyBatch( cipheredKeys, shares1, { public_key } ); + std::vector< bool > verificationsKey1 = + te_instance.VerifyBatch( cipheredKeys, shares1, { publicKey } ); BOOST_REQUIRE( std::all_of( - verifications_key1.begin(), verifications_key1.end(), []( bool v ) { return v; } ) ); + verificationsKey1.begin(), verificationsKey1.end(), []( bool v ) { return v; } ) ); std::vector< std::pair< libBLS::algebra::G2Point, size_t > > shares; - shares.push_back( std::make_pair( decryption_share, size_t( 1 ) ) ); + shares.push_back( std::make_pair( decryptionShare, size_t( 1 ) ) ); libBLS::AES256Key res = te_instance.CombineShares( cipheredKey, shares ); - BOOST_REQUIRE( res == random_aes_key ); + BOOST_REQUIRE( res == randomAesKey ); } } @@ -286,35 +667,122 @@ BOOST_AUTO_TEST_CASE( SimpleEncryptionWithAES ) { libBLS::TE te_instance = libBLS::TE( 1, 1 ); std::string message = "Hello, SKALE users and fans, gl!Hello, SKALE users and fans, gl!"; - std::vector< uint8_t > message_bytes( message.begin(), message.end() ); + std::vector< uint8_t > messageBytes( message.begin(), message.end() ); - libBLS::algebra::FrScalar secret_key = libBLS::algebra::FrScalar::random(); + libBLS::algebra::FrScalar secretKey = libBLS::algebra::FrScalar::random(); - libBLS::algebra::G2Point public_key = secret_key * libBLS::algebra::G2Point::generator(); + libBLS::algebra::G2Point publicKey = secretKey * libBLS::algebra::G2Point::generator(); - libBLS::CipherResult ciphertext_with_aes = - te_instance.encryptWithAES( message_bytes, public_key ); + libBLS::CipherResult ciphertextWithAes = te_instance.encryptWithAES( messageBytes, publicKey ); - auto encrypted_message = ciphertext_with_aes.ciphertext->getData(); - for ( const auto& cipheredKey : ciphertext_with_aes.ciphertext->getKeys() ) { - libBLS::algebra::G2Point decryption_share = - te_instance.getDecryptionShare( cipheredKey, secret_key ); + auto encryptedMessage = ciphertextWithAes.ciphertext->getData(); + for ( const auto& cipheredKey : ciphertextWithAes.ciphertext->getKeys() ) { + libBLS::algebra::G2Point decryptionShare = + te_instance.getDecryptionShare( cipheredKey, secretKey ); - BOOST_REQUIRE( te_instance.Verify( cipheredKey, decryption_share, public_key ) ); + BOOST_REQUIRE( te_instance.Verify( cipheredKey, decryptionShare, publicKey ) ); std::vector< std::pair< libBLS::algebra::G2Point, size_t > > shares; - shares.push_back( std::make_pair( decryption_share, size_t( 1 ) ) ); + shares.push_back( std::make_pair( decryptionShare, size_t( 1 ) ) ); - libBLS::AES256Key decrypted_aes_key = te_instance.CombineShares( cipheredKey, shares ); + libBLS::AES256Key decryptedAesKey = te_instance.CombineShares( cipheredKey, shares ); - libBLS::AesGcmCipher aesGcmCipher{ decrypted_aes_key }; - std::vector< uint8_t > plaintext = aesGcmCipher.decrypt( encrypted_message ); + libBLS::AesGcmCipher aesGcmCipher{ decryptedAesKey }; + std::vector< uint8_t > plaintext = aesGcmCipher.decrypt( encryptedMessage ); // append random secret to end of original message - libBLS::RandSecret rand_secret = ciphertext_with_aes.random_secret; - message_bytes.insert( message_bytes.end(), rand_secret.begin(), rand_secret.end() ); + libBLS::RandSecret randSecret = ciphertextWithAes.randomSecret; + messageBytes.insert( messageBytes.end(), randSecret.begin(), randSecret.end() ); + + BOOST_REQUIRE( plaintext == messageBytes ); + } +} + +BOOST_AUTO_TEST_CASE( EncryptionWithAES_AAD ) { + libBLS::TE te_instance = libBLS::TE( 1, 1 ); + + std::string message = "Hello, SKALE users! This is a test with AAD!"; + std::vector< uint8_t > messageBytes( message.begin(), message.end() ); + + // AAD that binds the ciphertext to a specific context (e.g., contract address) + std::vector< uint8_t > aad = { 0xDE, 0xAD, 0xBE, 0xEF, 0xCA, 0xFE, 0xBA, 0xBE }; + + libBLS::algebra::FrScalar secretKey = libBLS::algebra::FrScalar::random(); + libBLS::algebra::G2Point publicKey = secretKey * libBLS::algebra::G2Point::generator(); + + libBLS::EncryptMetaData encryptMeta; + encryptMeta.associatedDataAesGcm = aad; + + // Encrypt with AAD + libBLS::CipherResult ciphertextWithAes = + te_instance.encryptWithAES( messageBytes, publicKey, encryptMeta ); + + auto encryptedMessage = ciphertextWithAes.ciphertext->getData(); + + for ( const auto& cipheredKey : ciphertextWithAes.ciphertext->getKeys() ) { + libBLS::algebra::G2Point decryptionShare = + te_instance.getDecryptionShare( cipheredKey, secretKey ); + + BOOST_REQUIRE( te_instance.Verify( cipheredKey, decryptionShare, publicKey ) ); + + std::vector< std::pair< libBLS::algebra::G2Point, size_t > > shares; + shares.push_back( std::make_pair( decryptionShare, size_t( 1 ) ) ); - BOOST_REQUIRE( plaintext == message_bytes ); + libBLS::AES256Key decryptedAesKey = te_instance.CombineShares( cipheredKey, shares ); + + // Decrypt with the same AAD - should succeed + libBLS::AesGcmCipher aesGcmCipher{ decryptedAesKey }; + std::vector< uint8_t > plaintext = aesGcmCipher.decrypt( encryptedMessage, aad ); + + // Append random secret to end of original message for comparison + libBLS::RandSecret randSecret = ciphertextWithAes.randomSecret; + std::vector< uint8_t > expectedMessage = messageBytes; + expectedMessage.insert( expectedMessage.end(), randSecret.begin(), randSecret.end() ); + + BOOST_REQUIRE( plaintext == expectedMessage ); + } +} + +BOOST_AUTO_TEST_CASE( EncryptionWithAES_WrongAAD ) { + libBLS::TE te_instance = libBLS::TE( 1, 1 ); + + std::string message = "Hello, SKALE users! This is a test with AAD!"; + std::vector< uint8_t > messageBytes( message.begin(), message.end() ); + + // AAD used for encryption + std::vector< uint8_t > aad = { 0xDE, 0xAD, 0xBE, 0xEF, 0xCA, 0xFE, 0xBA, 0xBE }; + // Wrong AAD for decryption + std::vector< uint8_t > wrong_aad = { 0x01, 0x02, 0x03, 0x04 }; + + libBLS::algebra::FrScalar secretKey = libBLS::algebra::FrScalar::random(); + libBLS::algebra::G2Point publicKey = secretKey * libBLS::algebra::G2Point::generator(); + + libBLS::EncryptMetaData encryptMeta; + encryptMeta.associatedDataAesGcm = aad; + + // Encrypt with AAD + libBLS::CipherResult ciphertextWithAes = + te_instance.encryptWithAES( messageBytes, publicKey, encryptMeta ); + + auto encryptedMessage = ciphertextWithAes.ciphertext->getData(); + + for ( const auto& cipheredKey : ciphertextWithAes.ciphertext->getKeys() ) { + libBLS::algebra::G2Point decryptionShare = + te_instance.getDecryptionShare( cipheredKey, secretKey ); + + std::vector< std::pair< libBLS::algebra::G2Point, size_t > > shares; + shares.push_back( std::make_pair( decryptionShare, size_t( 1 ) ) ); + + libBLS::AES256Key decryptedAesKey = te_instance.CombineShares( cipheredKey, shares ); + + // Decrypt with wrong AAD - should fail + libBLS::AesGcmCipher aesGcmCipher{ decryptedAesKey }; + BOOST_REQUIRE_THROW( + aesGcmCipher.decrypt( encryptedMessage, wrong_aad ), std::runtime_error ); + + // Decrypt without AAD - should also fail + BOOST_REQUIRE_THROW( + aesGcmCipher.decrypt( encryptedMessage, std::nullopt ), std::runtime_error ); } } @@ -322,30 +790,30 @@ BOOST_AUTO_TEST_CASE( encryptionWithAESWrongKey ) { libBLS::TE te_instance = libBLS::TE( 1, 1 ); std::string message = "Hello, SKALE users and fans, gl!Hello, SKALE users and fans, gl!"; - std::vector< uint8_t > message_bytes( message.begin(), message.end() ); + std::vector< uint8_t > messageBytes( message.begin(), message.end() ); - libBLS::algebra::FrScalar secret_key = libBLS::algebra::FrScalar::random(); + libBLS::algebra::FrScalar secretKey = libBLS::algebra::FrScalar::random(); - libBLS::algebra::G2Point public_key = secret_key * libBLS::algebra::G2Point::generator(); + libBLS::algebra::G2Point publicKey = secretKey * libBLS::algebra::G2Point::generator(); - auto ciphertext_with_aes = te_instance.encryptWithAES( message_bytes, public_key ); + auto ciphertextWithAes = te_instance.encryptWithAES( messageBytes, publicKey ); - auto encrypted_message = ciphertext_with_aes.ciphertext->getData(); - for ( const auto& cipheredKey : ciphertext_with_aes.ciphertext->getKeys() ) { - libBLS::algebra::G2Point decryption_share = - te_instance.getDecryptionShare( cipheredKey, secret_key ); + auto encryptedMessage = ciphertextWithAes.ciphertext->getData(); + for ( const auto& cipheredKey : ciphertextWithAes.ciphertext->getKeys() ) { + libBLS::algebra::G2Point decryptionShare = + te_instance.getDecryptionShare( cipheredKey, secretKey ); - BOOST_REQUIRE( te_instance.Verify( cipheredKey, decryption_share, public_key ) ); + BOOST_REQUIRE( te_instance.Verify( cipheredKey, decryptionShare, publicKey ) ); std::vector< std::pair< libBLS::algebra::G2Point, size_t > > shares; - shares.push_back( std::make_pair( decryption_share, size_t( 1 ) ) ); + shares.push_back( std::make_pair( decryptionShare, size_t( 1 ) ) ); - libBLS::AES256Key random_aes_key; - RAND_bytes( random_aes_key.data(), random_aes_key.size() ); + libBLS::AES256Key randomAesKey; + RAND_bytes( randomAesKey.data(), randomAesKey.size() ); - libBLS::AesGcmCipher cipher{ random_aes_key }; - BOOST_REQUIRE_THROW( cipher.decrypt( encrypted_message ), std::runtime_error ); + libBLS::AesGcmCipher cipher{ randomAesKey }; + BOOST_REQUIRE_THROW( cipher.decrypt( encryptedMessage ), std::runtime_error ); } } @@ -353,33 +821,31 @@ BOOST_AUTO_TEST_CASE( encryptionWithAESWrongCiphertext ) { libBLS::TE te_instance = libBLS::TE( 1, 1 ); std::string message = "Hello, SKALE users and fans, gl!Hello, SKALE users and fans, gl!"; - std::vector< uint8_t > message_bytes( message.begin(), message.end() ); + std::vector< uint8_t > messageBytes( message.begin(), message.end() ); - libBLS::algebra::FrScalar secret_key = libBLS::algebra::FrScalar::random(); + libBLS::algebra::FrScalar secretKey = libBLS::algebra::FrScalar::random(); - libBLS::algebra::G2Point public_key = secret_key * libBLS::algebra::G2Point::generator(); + libBLS::algebra::G2Point publicKey = secretKey * libBLS::algebra::G2Point::generator(); - libBLS::CipherResult ciphertext_with_aes = - te_instance.encryptWithAES( message_bytes, public_key ); + libBLS::CipherResult ciphertextWithAes = te_instance.encryptWithAES( messageBytes, publicKey ); - for ( const auto& cipheredKey : ciphertext_with_aes.ciphertext->getKeys() ) { - libBLS::algebra::G2Point decryption_share = - te_instance.getDecryptionShare( cipheredKey, secret_key ); - BOOST_REQUIRE( te_instance.Verify( cipheredKey, decryption_share, public_key ) ); + for ( const auto& cipheredKey : ciphertextWithAes.ciphertext->getKeys() ) { + libBLS::algebra::G2Point decryptionShare = + te_instance.getDecryptionShare( cipheredKey, secretKey ); + BOOST_REQUIRE( te_instance.Verify( cipheredKey, decryptionShare, publicKey ) ); std::vector< std::pair< libBLS::algebra::G2Point, size_t > > shares; - shares.push_back( std::make_pair( decryption_share, size_t( 1 ) ) ); - libBLS::AES256Key decrypted_aes_key = te_instance.CombineShares( cipheredKey, shares ); + shares.push_back( std::make_pair( decryptionShare, size_t( 1 ) ) ); + libBLS::AES256Key decryptedAesKey = te_instance.CombineShares( cipheredKey, shares ); - std::string bad_message = - "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"; - std::vector< uint8_t > bad_message_bytes( message.begin(), message.end() ); + std::string badMessage = "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"; + std::vector< uint8_t > badMessageBytes( message.begin(), message.end() ); - auto bad_encrypted_message = - te_instance.encryptWithAES( bad_message_bytes, public_key ).ciphertext->getData(); + auto bad_encryptedMessage = + te_instance.encryptWithAES( badMessageBytes, publicKey ).ciphertext->getData(); - libBLS::AesGcmCipher cipher{ decrypted_aes_key }; - BOOST_REQUIRE_THROW( cipher.decrypt( bad_encrypted_message ), std::runtime_error ); + libBLS::AesGcmCipher cipher{ decryptedAesKey }; + BOOST_REQUIRE_THROW( cipher.decrypt( bad_encryptedMessage ), std::runtime_error ); } } @@ -387,40 +853,40 @@ BOOST_AUTO_TEST_CASE( EncryptionCipherToBytes ) { libBLS::TE te_instance = libBLS::TE( 1, 1 ); std::string message = "Hello, SKALE users and fans, gl!Hello, SKALE users and fans, gl!"; - std::vector< uint8_t > message_bytes( message.begin(), message.end() ); + std::vector< uint8_t > messageBytes( message.begin(), message.end() ); - libBLS::algebra::FrScalar secret_key = libBLS::algebra::FrScalar::random(); + libBLS::algebra::FrScalar secretKey = libBLS::algebra::FrScalar::random(); - libBLS::algebra::G2Point public_key = secret_key * libBLS::algebra::G2Point::generator(); + libBLS::algebra::G2Point publicKey = secretKey * libBLS::algebra::G2Point::generator(); - std::string common_public_str = public_key.toString( libBLS::Base::HEXA ); - auto result = te_instance.encryptMessage( message_bytes, common_public_str ); - libBLS::RandSecret rand_secret = result.second; + std::string commonPublicStr = publicKey.toString( libBLS::Base::HEXA ); + auto result = te_instance.encryptMessage( messageBytes, commonPublicStr ); + libBLS::RandSecret randSecret = result.second; - std::vector< uint8_t > encrypted_msg_bytes = + std::vector< uint8_t > encryptedMsgBytes = libBLS::ThresholdUtils::hexCStringToBytes( result.first.c_str() ); - libBLS::Ciphertext ciphertext = libBLS::Ciphertext::fromBytes( encrypted_msg_bytes ); - auto encrypted_message = ciphertext.getData(); + libBLS::Ciphertext ciphertext = libBLS::Ciphertext::fromBytes( encryptedMsgBytes ); + auto encryptedMessage = ciphertext.getData(); for ( const auto& cipheredkey : ciphertext.getKeys() ) { - libBLS::algebra::G2Point decryption_share = - te_instance.getDecryptionShare( cipheredkey, secret_key ); + libBLS::algebra::G2Point decryptionShare = + te_instance.getDecryptionShare( cipheredkey, secretKey ); - BOOST_REQUIRE( te_instance.Verify( cipheredkey, decryption_share, public_key ) ); + BOOST_REQUIRE( te_instance.Verify( cipheredkey, decryptionShare, publicKey ) ); std::vector< std::pair< libBLS::algebra::G2Point, size_t > > shares; - shares.push_back( std::make_pair( decryption_share, size_t( 1 ) ) ); + shares.push_back( std::make_pair( decryptionShare, size_t( 1 ) ) ); - libBLS::AES256Key decrypted_aes_key = te_instance.CombineShares( cipheredkey, shares ); + libBLS::AES256Key decryptedAesKey = te_instance.CombineShares( cipheredkey, shares ); - libBLS::AesGcmCipher cipher{ decrypted_aes_key }; - std::vector< uint8_t > plaintext = cipher.decrypt( encrypted_message ); + libBLS::AesGcmCipher cipher{ decryptedAesKey }; + std::vector< uint8_t > plaintext = cipher.decrypt( encryptedMessage ); // append random secret to the message - message_bytes.insert( message_bytes.end(), rand_secret.begin(), rand_secret.end() ); + messageBytes.insert( messageBytes.end(), randSecret.begin(), randSecret.end() ); - BOOST_REQUIRE( plaintext == message_bytes ); + BOOST_REQUIRE( plaintext == messageBytes ); } } @@ -437,7 +903,7 @@ BOOST_AUTO_TEST_CASE( ThresholdEncryptionReal ) { } } - std::vector< libBLS::algebra::FrScalar > secret_keys( n ); + std::vector< libBLS::algebra::FrScalar > secretKeys( n ); for ( size_t i = 0; i < 16; ++i ) { libBLS::algebra::FrScalar sk = libBLS::algebra::FrScalar::zero(); @@ -452,35 +918,35 @@ BOOST_AUTO_TEST_CASE( ThresholdEncryptionReal ) { sk += tmp4; } - secret_keys[i] = sk; + secretKeys[i] = sk; } - libBLS::algebra::FrScalar common_secret = coeffs[0]; + libBLS::algebra::FrScalar commonSecret = coeffs[0]; - libBLS::algebra::G2Point common_public = common_secret * libBLS::algebra::G2Point::generator(); + libBLS::algebra::G2Point commonPublic = commonSecret * libBLS::algebra::G2Point::generator(); libBLS::AES256Key key; RAND_bytes( key.data(), key.size() ); - auto result = obj.getCiphertext( key, common_public ); + auto result = obj.getCiphertext( key, commonPublic, std::nullopt, std::nullopt ); for ( const auto& cipheredKey : result.ciphertext ) { std::vector< std::pair< libBLS::algebra::G2Point, size_t > > shares( t ); - std::vector< libBLS::algebra::G2Point > decrypted_shares( t ); + std::vector< libBLS::algebra::G2Point > decryptedShares( t ); std::vector< libBLS::algebra::G2Point > pubKeys( t ); for ( size_t i = 0; i < t; ++i ) { libBLS::algebra::G2Point decrypted = - obj.getDecryptionShare( cipheredKey, secret_keys[i] ); + obj.getDecryptionShare( cipheredKey, secretKeys[i] ); - decrypted_shares[i] = decrypted; + decryptedShares[i] = decrypted; - libBLS::algebra::G2Point public_key = - secret_keys[i] * libBLS::algebra::G2Point::generator(); + libBLS::algebra::G2Point publicKey = + secretKeys[i] * libBLS::algebra::G2Point::generator(); - pubKeys[i] = public_key; + pubKeys[i] = publicKey; - BOOST_REQUIRE( obj.Verify( cipheredKey, decrypted, public_key ) ); + BOOST_REQUIRE( obj.Verify( cipheredKey, decrypted, publicKey ) ); shares[i].first = decrypted; @@ -491,7 +957,7 @@ BOOST_AUTO_TEST_CASE( ThresholdEncryptionReal ) { std::vector< libBLS::CipheredKey > keysBatch; keysBatch.push_back( cipheredKey ); - auto verifications = obj.VerifyBatch( keysBatch, decrypted_shares, pubKeys ); + auto verifications = obj.VerifyBatch( keysBatch, decryptedShares, pubKeys ); BOOST_REQUIRE( std::all_of( verifications.begin(), verifications.end(), []( bool v ) { return v; } ) ); @@ -519,7 +985,7 @@ BOOST_AUTO_TEST_CASE( ThresholdEncryptionRandomPK ) { } } - std::vector< libBLS::algebra::FrScalar > secret_keys( 16 ); + std::vector< libBLS::algebra::FrScalar > secretKeys( 16 ); for ( size_t i = 0; i < 16; ++i ) { libBLS::algebra::FrScalar sk = libBLS::algebra::FrScalar::zero(); @@ -534,15 +1000,15 @@ BOOST_AUTO_TEST_CASE( ThresholdEncryptionRandomPK ) { sk += tmp4; } - secret_keys[i] = sk; + secretKeys[i] = sk; } - libBLS::algebra::G2Point common_public = libBLS::algebra::G2Point::random(); + libBLS::algebra::G2Point commonPublic = libBLS::algebra::G2Point::random(); libBLS::AES256Key key; RAND_bytes( key.data(), key.size() ); - auto result = obj.getCiphertext( key, common_public ); + auto result = obj.getCiphertext( key, commonPublic, std::nullopt, std::nullopt ); std::vector< std::pair< libBLS::algebra::G2Point, size_t > > shares( 11 ); @@ -550,11 +1016,11 @@ BOOST_AUTO_TEST_CASE( ThresholdEncryptionRandomPK ) { for ( const auto& cipheredKey : result.ciphertext ) { for ( size_t i = 0; i < 11; ++i ) { libBLS::algebra::G2Point decrypted = - obj.getDecryptionShare( cipheredKey, secret_keys[i] ); - libBLS::algebra::G2Point public_key = - secret_keys[i] * libBLS::algebra::G2Point::generator(); + obj.getDecryptionShare( cipheredKey, secretKeys[i] ); + libBLS::algebra::G2Point publicKey = + secretKeys[i] * libBLS::algebra::G2Point::generator(); - BOOST_REQUIRE( obj.Verify( cipheredKey, decrypted, public_key ) ); + BOOST_REQUIRE( obj.Verify( cipheredKey, decrypted, publicKey ) ); shares[i].first = decrypted; @@ -578,7 +1044,7 @@ BOOST_AUTO_TEST_CASE( ThresholdEncryptionRandomSK ) { } } - std::vector< libBLS::algebra::FrScalar > secret_keys( 16 ); + std::vector< libBLS::algebra::FrScalar > secretKeys( 16 ); for ( size_t i = 0; i < 16; ++i ) { libBLS::algebra::FrScalar sk = libBLS::algebra::FrScalar::zero(); @@ -593,33 +1059,33 @@ BOOST_AUTO_TEST_CASE( ThresholdEncryptionRandomSK ) { sk += tmp4; } - // let secret_key[7] be a random generated value instead of correctly generated + // let secretKey[7] be a random generated value instead of correctly generated if ( i == 7 ) { sk = libBLS::algebra::FrScalar::random(); } - secret_keys[i] = sk; + secretKeys[i] = sk; } - libBLS::algebra::FrScalar common_secret = coeffs[0]; + libBLS::algebra::FrScalar commonSecret = coeffs[0]; - libBLS::algebra::G2Point common_public = common_secret * libBLS::algebra::G2Point::generator(); + libBLS::algebra::G2Point commonPublic = commonSecret * libBLS::algebra::G2Point::generator(); libBLS::AES256Key key; RAND_bytes( key.data(), key.size() ); - auto result = obj.getCiphertext( key, common_public ); + auto result = obj.getCiphertext( key, commonPublic, std::nullopt, std::nullopt ); for ( const auto& cipheredKey : result.ciphertext ) { std::vector< std::pair< libBLS::algebra::G2Point, size_t > > shares( 11 ); for ( size_t i = 0; i < 11; ++i ) { libBLS::algebra::G2Point decrypted = - obj.getDecryptionShare( cipheredKey, secret_keys[i] ); - libBLS::algebra::G2Point public_key = - secret_keys[i] * libBLS::algebra::G2Point::generator(); + obj.getDecryptionShare( cipheredKey, secretKeys[i] ); + libBLS::algebra::G2Point publicKey = + secretKeys[i] * libBLS::algebra::G2Point::generator(); - BOOST_REQUIRE( obj.Verify( cipheredKey, decrypted, public_key ) ); + BOOST_REQUIRE( obj.Verify( cipheredKey, decrypted, publicKey ) ); shares[i].first = decrypted; @@ -643,7 +1109,7 @@ BOOST_AUTO_TEST_CASE( ThresholdEncryptionCorruptedCiphertext ) { } } - std::vector< libBLS::algebra::FrScalar > secret_keys( 16 ); + std::vector< libBLS::algebra::FrScalar > secretKeys( 16 ); for ( size_t i = 0; i < 16; ++i ) { libBLS::algebra::FrScalar sk = libBLS::algebra::FrScalar::zero(); @@ -658,63 +1124,63 @@ BOOST_AUTO_TEST_CASE( ThresholdEncryptionCorruptedCiphertext ) { sk += tmp4; } - secret_keys[i] = sk; + secretKeys[i] = sk; } - libBLS::algebra::FrScalar common_secret = coeffs[0]; + libBLS::algebra::FrScalar commonSecret = coeffs[0]; - libBLS::algebra::G2Point common_public = common_secret * libBLS::algebra::G2Point::identity(); + libBLS::algebra::G2Point commonPublic = commonSecret * libBLS::algebra::G2Point::identity(); libBLS::AES256Key key; RAND_bytes( key.data(), key.size() ); - auto result = obj.getCiphertext( key, common_public ); + auto result = obj.getCiphertext( key, commonPublic, std::nullopt, std::nullopt ); libBLS::algebra::G1Point rand = libBLS::algebra::G1Point::random(); libBLS::CipheredKey cipheredKeyToCorrupt = result.ciphertext[0]; - libBLS::CipheredKey corrupted_ciphered_key = { cipheredKeyToCorrupt.U, cipheredKeyToCorrupt.V, + libBLS::CipheredKey corruptedCipheredKey = { cipheredKeyToCorrupt.U, cipheredKeyToCorrupt.V, rand }; for ( size_t i = 0; i < 11; ++i ) { libBLS::algebra::G2Point decryptedWrong = - obj.getDecryptionShare( corrupted_ciphered_key, secret_keys[i] ); + obj.getDecryptionShare( corruptedCipheredKey, secretKeys[i] ); libBLS::algebra::G2Point decryptedCorrect = - obj.getDecryptionShare( cipheredKeyToCorrupt, secret_keys[i] ); + obj.getDecryptionShare( cipheredKeyToCorrupt, secretKeys[i] ); - libBLS::algebra::G2Point public_key = secret_keys[i] * libBLS::algebra::G2Point::identity(); + libBLS::algebra::G2Point publicKey = secretKeys[i] * libBLS::algebra::G2Point::identity(); // wrong cipher key, correct decrypted key - should return false - BOOST_REQUIRE( !obj.Verify( corrupted_ciphered_key, decryptedCorrect, public_key ) ); + BOOST_REQUIRE( !obj.Verify( corruptedCipheredKey, decryptedCorrect, publicKey ) ); // wrong decrypted key, correct cipher key - should return false - BOOST_REQUIRE( !obj.Verify( cipheredKeyToCorrupt, decryptedWrong, public_key ) ); + BOOST_REQUIRE( !obj.Verify( cipheredKeyToCorrupt, decryptedWrong, publicKey ) ); } } BOOST_AUTO_TEST_CASE( LagrangeInterpolationExceptions ) { for ( size_t i = 0; i < 100; i++ ) { - std::default_random_engine rand_gen( ( unsigned int ) time( 0 ) ); - size_t num_all = rand_gen() % 15 + 2; - size_t num_signed = rand_gen() % ( num_all - 1 ) + 2; + std::default_random_engine randGen( ( unsigned int ) time( 0 ) ); + size_t numAll = randGen() % 15 + 2; + size_t numSigned = randGen() % ( numAll - 1 ) + 2; { - libBLS::TE obj( num_signed, num_all ); + libBLS::TE obj( numSigned, numAll ); std::vector< size_t > vect; - for ( size_t i = 0; i < num_signed - 1; i++ ) + for ( size_t i = 0; i < numSigned - 1; i++ ) vect.push_back( i + 1 ); - BOOST_REQUIRE_THROW( libBLS::algebra::lagrangeCoeffs( vect, num_signed ), + BOOST_REQUIRE_THROW( libBLS::algebra::lagrangeCoeffs( vect, numSigned ), libBLS::ThresholdUtils::IncorrectInput ); } { - libBLS::TE obj( num_signed, num_all ); + libBLS::TE obj( numSigned, numAll ); std::vector< size_t > vect; - for ( size_t i = 0; i < num_signed; i++ ) { + for ( size_t i = 0; i < numSigned; i++ ) { vect.push_back( i + 1 ); } vect.at( 1 ) = vect.at( 0 ); - BOOST_REQUIRE_THROW( libBLS::algebra::lagrangeCoeffs( vect, num_signed ), + BOOST_REQUIRE_THROW( libBLS::algebra::lagrangeCoeffs( vect, numSigned ), libBLS::ThresholdUtils::IncorrectInput ); } } diff --git a/threshold_encryption/AesGcmCipher.cpp b/threshold_encryption/AesGcmCipher.cpp index 929adf5f..f88a5826 100644 --- a/threshold_encryption/AesGcmCipher.cpp +++ b/threshold_encryption/AesGcmCipher.cpp @@ -23,11 +23,80 @@ #include "threshold_encryption/AesGcmCipher.h" #include "tools/utils.h" +#include +#include #include namespace libBLS { -std::vector< uint8_t > AesGcmCipher::encrypt( const std::vector< uint8_t >& plaintext ) { +AesGcmCipher::AesGcmCipher() : isDeterministic( false ), encryptCounter( 0 ) { + initAES(); + // Generate random key + if ( RAND_bytes( key.data(), key.size() ) != 1 ) { + throw std::runtime_error( "Failed to generate random key" ); + } + // IV will be generated randomly on each encrypt() call + iv.fill( 0 ); +} + +AesGcmCipher::AesGcmCipher( const AES256Key& rawKey ) + : key( rawKey ), isDeterministic( false ), encryptCounter( 0 ) { + initAES(); + // IV will be generated randomly on each encrypt() call (or extracted from ciphertext for + // decrypt) + iv.fill( 0 ); +} + +AesGcmCipher::AesGcmCipher( const Seed256& seed ) : isDeterministic( true ), encryptCounter( 0 ) { + initAES(); + + // Use HKDF to derive key deterministically from seed + // IV will be derived per-encryption using Synthetic IV (HMAC of plaintext) + EVP_PKEY_CTX* pctx = EVP_PKEY_CTX_new_id( EVP_PKEY_HKDF, nullptr ); + if ( !pctx ) { + throw std::runtime_error( "Failed to create HKDF context" ); + } + + // Using a scope guard pattern for cleanup + struct CtxGuard { + EVP_PKEY_CTX* ctx; + ~CtxGuard() { + if ( ctx ) + EVP_PKEY_CTX_free( ctx ); + } + } guard{ pctx }; + + if ( EVP_PKEY_derive_init( pctx ) <= 0 ) { + throw std::runtime_error( "Failed to initialize HKDF" ); + } + if ( EVP_PKEY_CTX_set_hkdf_md( pctx, EVP_sha256() ) <= 0 ) { + throw std::runtime_error( "Failed to set HKDF hash" ); + } + // Salt is optional but recommended - using a fixed salt for determinism + static const unsigned char salt[] = "AesGcmCipher-v1"; + if ( EVP_PKEY_CTX_set1_hkdf_salt( pctx, salt, sizeof( salt ) - 1 ) <= 0 ) { + throw std::runtime_error( "Failed to set HKDF salt" ); + } + if ( EVP_PKEY_CTX_set1_hkdf_key( pctx, seed.data.data(), seed.data.size() ) <= 0 ) { + throw std::runtime_error( "Failed to set HKDF key" ); + } + // Info parameter for domain separation - only deriving the encryption key + static const unsigned char info[] = "encryption-key"; + if ( EVP_PKEY_CTX_add1_hkdf_info( pctx, info, sizeof( info ) - 1 ) <= 0 ) { + throw std::runtime_error( "Failed to set HKDF info" ); + } + + size_t outlen = AES_256_KEY_SIZE_BYTES; + if ( EVP_PKEY_derive( pctx, key.data(), &outlen ) <= 0 || outlen != AES_256_KEY_SIZE_BYTES ) { + throw std::runtime_error( "Failed to derive key material" ); + } + + // IV is not stored - it will be derived per-encryption from plaintext + iv.fill( 0 ); +} + +std::vector< uint8_t > AesGcmCipher::encrypt( + const std::vector< uint8_t >& plaintext, const std::optional< std::vector< uint8_t > >& aad ) { initAES(); // Make sure there is enough space for: IV + plaintext + padding @@ -36,11 +105,35 @@ std::vector< uint8_t > AesGcmCipher::encrypt( const std::vector< uint8_t >& plai std::vector< unsigned char > output; output.resize( enc_length, '\0' ); - // Initialize IV vector - unsigned char iv[AES_GCM_IV_SIZE]; - RAND_bytes( iv, AES_GCM_IV_SIZE ); + // Compute IV: Synthetic IV (HMAC of plaintext + counter) for deterministic mode, random + // otherwise + unsigned char localIv[AES_GCM_IV_SIZE]; + if ( isDeterministic ) { + // Synthetic IV: HMAC-SHA256(key, plaintext || counter) truncated to 12 bytes + // Counter ensures same plaintext encrypted multiple times gets different IVs + // As long as encrypt() is called in the same order, they produce identical output + unsigned char hmacResult[32]; + unsigned int hmacLen = 0; + + // Create input: plaintext || counter (8 bytes, big-endian) + // Append counter as 8 bytes in big-endian order + std::vector< uint8_t > hmacInput( plaintext.begin(), plaintext.end() ); + for ( int byteIndex = 7; byteIndex >= 0; --byteIndex ) { + hmacInput.push_back( + static_cast< uint8_t >( ( encryptCounter >> ( byteIndex * 8 ) ) & 0xFF ) ); + } + + if ( !HMAC( EVP_sha256(), key.data(), key.size(), hmacInput.data(), hmacInput.size(), + hmacResult, &hmacLen ) ) { + throw std::runtime_error( "Failed to compute synthetic IV" ); + } + std::copy( hmacResult, hmacResult + AES_GCM_IV_SIZE, localIv ); + ++encryptCounter; + } else { + RAND_bytes( localIv, AES_GCM_IV_SIZE ); + } // Place IV at start of output - std::copy( iv, iv + AES_GCM_IV_SIZE, output.begin() ); + std::copy( localIv, localIv + AES_GCM_IV_SIZE, output.begin() ); // Account offset for the IV already stored in output vec size_t offset = AES_GCM_IV_SIZE; @@ -58,8 +151,17 @@ std::vector< uint8_t > AesGcmCipher::encrypt( const std::vector< uint8_t >& plai check( EVP_CIPHER_CTX_ctrl( e_ctx.get(), EVP_CTRL_GCM_SET_IVLEN, AES_GCM_IV_SIZE, nullptr ), "Failed to set IV length" ); // now actually supply key & IV: - check( EVP_EncryptInit_ex( e_ctx.get(), nullptr, nullptr, key.data(), iv ), + check( EVP_EncryptInit_ex( e_ctx.get(), nullptr, nullptr, key.data(), localIv ), "Failed to set key/IV" ); + + // Process AAD if provided (authenticated but not encrypted) + if ( aad.has_value() && !aad->empty() ) { + int aad_len = 0; + check( EVP_EncryptUpdate( + e_ctx.get(), nullptr, &aad_len, aad->data(), static_cast< int >( aad->size() ) ), + "Failed to process AAD" ); + } + // Cypher data and store in output check( EVP_EncryptUpdate( e_ctx.get(), &output[offset], &outlen, ( const unsigned char* ) plaintext.data(), plaintext.size() ), @@ -84,7 +186,8 @@ std::vector< uint8_t > AesGcmCipher::encrypt( const std::vector< uint8_t >& plai return std::vector< uint8_t >( output ); } -std::vector< uint8_t > AesGcmCipher::decrypt( const std::vector< uint8_t >& ciphertext ) { +std::vector< uint8_t > AesGcmCipher::decrypt( + const std::vector< uint8_t >& ciphertext, const std::optional< std::vector< uint8_t > >& aad ) { initAES(); constexpr size_t meta = AES_GCM_IV_SIZE + AES_GCM_TAG_SIZE; @@ -118,6 +221,15 @@ std::vector< uint8_t > AesGcmCipher::decrypt( const std::vector< uint8_t >& ciph "Failed to set IV length" ); check( EVP_DecryptInit_ex( d_ctx.get(), nullptr, nullptr, key.data(), iv ), "Failed to set key/IV" ); + + // Process AAD if provided (must match what was used during encryption) + if ( aad.has_value() && !aad->empty() ) { + int aad_len = 0; + check( EVP_DecryptUpdate( + d_ctx.get(), nullptr, &aad_len, aad->data(), static_cast< int >( aad->size() ) ), + "Failed to process AAD" ); + } + // Decrypt body check( EVP_DecryptUpdate( d_ctx.get(), plaintext.data(), &len, body_ptr, body_len ), "Failed to decrypt data" ); diff --git a/threshold_encryption/AesGcmCipher.h b/threshold_encryption/AesGcmCipher.h index 8b0d2294..40ac6642 100644 --- a/threshold_encryption/AesGcmCipher.h +++ b/threshold_encryption/AesGcmCipher.h @@ -30,10 +30,11 @@ #include #include #include +#include #include #include -#include "tools/utils.h" +#include namespace libBLS { @@ -43,6 +44,12 @@ constexpr size_t AES_GCM_TAG_SIZE = 16; using AES256Key = std::array< uint8_t, AES_256_KEY_SIZE_BYTES >; +/// Strong type wrapper for 256-bit seed (used for deterministic key derivation) +/// This seed is used to derive both the AES key (via HKDF) and the TE scalar secret. +struct Seed256 { + std::array< uint8_t, AES_256_KEY_SIZE_BYTES > data; +}; + /// ------------------------------- /// EVP_CONTEXT wrapper @@ -58,24 +65,48 @@ struct EvpContextDeleter { using UniqueCtx = std::unique_ptr< EVP_CIPHER_CTX, EvpContextDeleter >; - class AesGcmCipher { private: AES256Key key; + // Whether the cipher is deterministic (i.e. uses HKDF to derive key from seed) + bool isDeterministic; + // Counter for deterministic IV generation + uint64_t encryptCounter; + std::array< uint8_t, AES_GCM_IV_SIZE > iv; + public: - AesGcmCipher( const AES256Key& key ) : key( key ) {} + /// @brief Creates cipher with random key (for encryption) + AesGcmCipher(); + + /// @brief Creates cipher with key derived from seed (for deterministic encryption) + /// @param seed The Seed256 wrapper containing the seed to derive key from using HKDF + explicit AesGcmCipher( const Seed256& seed ); + + /// @brief Creates cipher with a known raw key (for decryption) + /// @param key The raw AES-256 key to use directly (no derivation) + explicit AesGcmCipher( const AES256Key& key ); + ~AesGcmCipher() = default; /// @brief Encrypts the given plaintext using AES-256-GCM - /// @param plaintext + /// @param plaintext The data to encrypt + /// @param aad Optional additional authenticated data (not encrypted, but authenticated) /// @return Encrypted message - std::vector< uint8_t > encrypt( const std::vector< uint8_t >& plaintext ); + std::vector< uint8_t > encrypt( const std::vector< uint8_t >& plaintext, + const std::optional< std::vector< uint8_t > >& aad = std::nullopt ); /// @brief Decrypts the given ciphertext using AES-256-GCM - /// @param ciphertext + /// @param ciphertext The data to decrypt + /// @param aad Optional additional authenticated data (must match what was used during + /// encryption) /// @return Decrypted message - std::vector< uint8_t > decrypt( const std::vector< uint8_t >& ciphertext ); + std::vector< uint8_t > decrypt( const std::vector< uint8_t >& ciphertext, + const std::optional< std::vector< uint8_t > >& aad = std::nullopt ); + + /// @brief Returns the AES key (for use as plaintext in threshold encryption) + /// @return Reference to the 32-byte AES key + const AES256Key& getKey() const { return key; } private: /// Helper function to check if the operation was successful diff --git a/threshold_encryption/CMakeLists.txt b/threshold_encryption/CMakeLists.txt index 5fc4b3ff..58d6684b 100644 --- a/threshold_encryption/CMakeLists.txt +++ b/threshold_encryption/CMakeLists.txt @@ -19,6 +19,8 @@ set(SOURCES TEPublicKeyShare.cpp ThresholdEncryption.cpp AesGcmCipher.cpp + CipheredKey.cpp + Ciphertext.cpp ) set(HEADERS @@ -33,6 +35,8 @@ set(HEADERS TEPublicKeyShare.h ThresholdEncryption.h AesGcmCipher.h + CipheredKey.h + Ciphertext.h ) set(PROJECT_VERSION 0.2.0) @@ -62,10 +66,10 @@ if (LIBBLS_BUILD_TESTS) # Helper function to add tests function(add_te_test name source) add_executable(${name} ${source} ../test/utils.cpp) # Includes utils only for tests - target_include_directories(${name} SYSTEM PRIVATE - ${THIRD_PARTY_DIR} + target_include_directories(${name} SYSTEM PRIVATE + ${THIRD_PARTY_DIR} boost_program_options - ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR} ../test ) @@ -85,13 +89,13 @@ if (LIBBLS_BUILD_TESTS) add_test(NAME te_tests COMMAND te_unit_test) if(NOT EMSCRIPTEN AND NOT APPLE) - add_executable(te_sample_sgx ../test/te_sample_sgx.cpp ../test/utils.cpp) + add_executable(te_sample_sgx ../test/te_sample_sgx.cpp ../test/utils.cpp) target_include_directories(te_sample_sgx SYSTEM PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${THIRD_PARTY_DIR} ../test) target_link_libraries(te_sample_sgx PRIVATE te bls ${CRYPTOPP_LIBRARY} jsonrpccpp-client jsonrpccpp-server jsonrpccpp-common jsoncpp curl pthread ssl crypto z idn2) endif() if(NOT EMSCRIPTEN AND NOT APPLE) - add_executable(decrypt_message ../tools/decryptMessage.cpp) + add_executable(decrypt_message ../tools/decryptMessage.cpp) target_include_directories(decrypt_message SYSTEM PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${THIRD_PARTY_DIR}) target_link_libraries(decrypt_message PRIVATE te bls ${CRYPTOPP_LIBRARY}) endif() @@ -119,6 +123,26 @@ if (LIBBLS_BUILD_TESTS) ) endif() +if (NOT EMSCRIPTEN) + # Build python threshold_encryption shared library + add_library(t_encrypt_python SHARED ../threshold_encryption/encryptMessage.cpp) + get_directory_property(PARENT_DIR PARENT_DIRECTORY) + target_include_directories(t_encrypt_python PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${PARENT_DIR}) + if(APPLE) + target_link_libraries(t_encrypt_python PRIVATE + -Wl,-force_load,$ + -Wl,-force_load,$ + -Wl,-force_load,$ + backend ${FOLLY_STATIC_LIBS} ${CRYPTOPP_LIBRARY}) + # Explicitly add dependencies for Apple builds because they are hidden inside linker flags + add_dependencies(t_encrypt_python te bls common_utils) + else() + target_link_libraries(t_encrypt_python PRIVATE + -Wl,--whole-archive te bls common_utils -Wl,--no-whole-archive + backend ${FOLLY_STATIC_LIBS} ${CRYPTOPP_LIBRARY}) + endif() +endif() + if (EMSCRIPTEN) add_executable(encrypt ../threshold_encryption/encryptMessage.cpp ../tools/utils.cpp) set_target_properties(encrypt PROPERTIES LINK_FLAGS "-s EXIT_RUNTIME=1 -s USE_PTHREADS=0 -s MODULARIZE -s ALLOW_MEMORY_GROWTH=1 -s NO_DISABLE_EXCEPTION_CATCHING=1 -s EXPORTED_RUNTIME_METHODS='[\"ccall\"]' -s EXPORTED_FUNCTIONS='[\"_encryptMessage\", \"_encryptMessageDualKey\",\"_encryptMessageMockup\"]' -s ENVIRONMENT='node,web' -s SINGLE_FILE=1") diff --git a/threshold_encryption/CipheredKey.cpp b/threshold_encryption/CipheredKey.cpp new file mode 100644 index 00000000..d717aed1 --- /dev/null +++ b/threshold_encryption/CipheredKey.cpp @@ -0,0 +1,93 @@ +#include +#include + +#include "AesGcmCipher.h" +#include "CipheredKey.h" +#include "backends/algebra.hpp" + +namespace libBLS { + + +std::array< uint8_t, CipheredKey::CIPHERED_KEY_SIZE_BYTES > CipheredKey::toBytes() const { + std::array< uint8_t, CIPHERED_KEY_SIZE_BYTES > bytes; + uint8_t* source = bytes.data(); + // set U component + auto uBytes = U.toByteArray(); + std::memcpy( source, uBytes.data(), uBytes.size() ); + source += uBytes.size(); + // set V commponent + std::memcpy( source, V.data(), V.size() ); + source += V.size(); + // set W component + auto wBytes = W.toByteArray(); + std::memcpy( source, wBytes.data(), wBytes.size() ); + + return bytes; +} + + +CipheredKey CipheredKey::fromBytes( + std::array< uint8_t, CIPHERED_KEY_SIZE_BYTES > _bytes, bool _validate ) { + std::array< uint8_t, G2_SIZE_BYTES > uBytes; + std::array< uint8_t, AES_256_KEY_SIZE_BYTES > vBytes; + std::array< uint8_t, G1_SIZE_BYTES > wBytes; + + uint8_t* offset = _bytes.data(); + // Get U bytes + std::memcpy( uBytes.data(), offset, G2_SIZE_BYTES ); + offset += G2_SIZE_BYTES; + // Get V bytes + std::memcpy( vBytes.data(), offset, AES_256_KEY_SIZE_BYTES ); + offset += AES_256_KEY_SIZE_BYTES; + // Get W bytes + std::memcpy( wBytes.data(), offset, G1_SIZE_BYTES ); + + // Convert to CipheredKey components + algebra::G2Point U = algebra::G2Point::fromBytes( uBytes ); + algebra::G1Point W = algebra::G1Point::fromBytes( wBytes ); + + // constructor performs validation + return CipheredKey( U, vBytes, W, _validate ); +} + +void CipheredKey::validate() const { + W.validate(); + U.validate(); +} + +CipheredKey CipheredKey::random() { + algebra::G2Point U = algebra::G2Point::random(); + AES256Key V; + RAND_bytes( V.data(), V.size() ); + algebra::G1Point W = algebra::G1Point::random(); + return CipheredKey( U, V, W ); +} + +std::string CipheredKey::getDecryptionShareInput() { + U.toAffineCoordinates(); + return U.toString( Base::HEXA ); +} + +std::vector< std::string > CipheredKey::getDecryptionShareInputBatch( + const std::vector< CipheredKey >& keys ) { + std::vector< algebra::G2Point > U_points; + U_points.reserve( keys.size() ); + for ( const auto& key : keys ) { + U_points.push_back( key.U ); + } + + // batch convert all to affine coordinates + toAffineVec( U_points ); + + std::vector< std::string > res; + res.reserve( keys.size() ); + for ( const auto& U : U_points ) { + auto uSplitted = U.toString( Base::HEXA ); + res.push_back( uSplitted ); + } + + return res; +} + + +} // namespace libBLS \ No newline at end of file diff --git a/threshold_encryption/CipheredKey.h b/threshold_encryption/CipheredKey.h new file mode 100644 index 00000000..6505b9a2 --- /dev/null +++ b/threshold_encryption/CipheredKey.h @@ -0,0 +1,72 @@ +#pragma once + +#include +#include + +#include "AesGcmCipher.h" +#include "backends/algebra.hpp" + +namespace libBLS { + +/** + * @brief Holds the AES key ciphered via + * Threshold Encryption + */ +struct CipheredKey { + static constexpr size_t CIPHERED_KEY_SIZE_BYTES = + algebra::G2Point::SIZE_BYTES + AES_256_KEY_SIZE_BYTES + algebra::G1Point::SIZE_BYTES; + + algebra::G2Point U; + AES256Key V; + algebra::G1Point W; + +public: + CipheredKey() = default; + CipheredKey( algebra::G2Point _U, AES256Key _V, algebra::G1Point _W, bool _validate = true ) + : U( _U ), V( std::move( _V ) ), W( _W ) { + if ( _validate ) + validate(); + } + + bool operator==( const CipheredKey& other ) const { + return ( U == other.U ) && ( V == other.V ) && ( W == other.W ); + } + + /** + * @brief Converts CipheredKey to bytes + * The byte format returned is: `[ U | V | W ]` + * where `U` and `W` are elements of G2 and G1 groups respectively, + * and `V` is a fixed size AES256Key. + * + * Final byte representation is always of fixed size. + */ + std::array< uint8_t, CIPHERED_KEY_SIZE_BYTES > toBytes() const; + + /** + * @brief Converts bytes to CipheredKey + */ + static CipheredKey fromBytes( + std::array< uint8_t, CIPHERED_KEY_SIZE_BYTES > _bytes, bool _validate = true ); + + /** + * @brief Validates the CipheredKey + * @throw NotWellFormed if the key is not well formed + */ + void validate() const; + + static CipheredKey random(); + + /** + * Converts U component of the key to string + */ + std::string getDecryptionShareInput(); + + static std::vector< std::string > getDecryptionShareInputBatch( + const std::vector< CipheredKey >& keys ); + + const algebra::G2Point& getU() const { return U; } + const AES256Key& getV() const { return V; } + const algebra::G1Point& getW() const { return W; } +}; + +} // namespace libBLS diff --git a/threshold_encryption/Ciphertext.cpp b/threshold_encryption/Ciphertext.cpp new file mode 100644 index 00000000..4cc6badb --- /dev/null +++ b/threshold_encryption/Ciphertext.cpp @@ -0,0 +1,125 @@ +#include "Ciphertext.h" + +namespace libBLS { + +const std::vector< uint8_t >& Ciphertext::getData() const { + if ( !data ) { + throw ThresholdUtils::IncorrectInput( "Cyphertext data is not initialized" ); + } + return *data; +} + + +const std::vector< uint8_t > Ciphertext::toBytes() const { + if ( !data ) { + throw ThresholdUtils::IncorrectInput( "Cyphertext data is not initialized" ); + } + + // Calculate total size needed + size_t totalSize = sizeof( uint8_t ) + // for number of keys + ( keys.size() * CipheredKey::CIPHERED_KEY_SIZE_BYTES ) + // for all keys + data->size(); // for data + + std::vector< uint8_t > bytes; + bytes.reserve( totalSize ); + + // Add number of keys + uint8_t numKeys = static_cast< uint8_t >( keys.size() ); + bytes.push_back( numKeys ); + + // Add each key + for ( const auto& key : keys ) { + std::array< uint8_t, CipheredKey::CIPHERED_KEY_SIZE_BYTES > keyBytes = key.toBytes(); + bytes.insert( bytes.end(), keyBytes.begin(), keyBytes.end() ); + } + + // Add data + bytes.insert( bytes.end(), data->begin(), data->end() ); + + return bytes; +} + + +Ciphertext Ciphertext::fromBytes( std::vector< uint8_t >& bytes, bool _validate ) { + // we require at least 1 byte for num_keys + one key + random secret + 1 byte for data + if ( bytes.size() <= + sizeof( uint8_t ) + CipheredKey::CIPHERED_KEY_SIZE_BYTES + RANDOM_SECRET_SIZE_BYTES ) { + throw ThresholdUtils::IncorrectInput( "Cyphertext data is too short" ); + } + + size_t offset = 0; + + // Get number of keys + uint8_t numKeys; + std::memcpy( &numKeys, bytes.data() + offset, sizeof( uint8_t ) ); + offset += sizeof( uint8_t ); + + if ( numKeys == 0 || numKeys > 2 ) + throw ThresholdUtils::IncorrectInput( "Ciphertext must contain exactly 1 or 2 keys" ); + + // Check that the input matches the number of keys + size_t expectedMinSize = sizeof( uint8_t ) + + ( numKeys * CipheredKey::CIPHERED_KEY_SIZE_BYTES ) + + RANDOM_SECRET_SIZE_BYTES + 1; // +1 for at least 1 byte of actual data + if ( bytes.size() < expectedMinSize ) + throw ThresholdUtils::IncorrectInput( + "Cyphertext data is too short for specified number of keys" ); + + // Extract keys + std::vector< CipheredKey > keys; + keys.reserve( numKeys ); + + for ( size_t i = 0; i < numKeys; ++i ) { + std::array< uint8_t, CipheredKey::CIPHERED_KEY_SIZE_BYTES > keyBytes; + std::memcpy( keyBytes.data(), bytes.data() + offset, CipheredKey::CIPHERED_KEY_SIZE_BYTES ); + offset += CipheredKey::CIPHERED_KEY_SIZE_BYTES; + + // do not validate CipheredKey here + // if validation is enabled, CipheredKey is validated in Ciphertext's constructor + // otherwise we don't need validation at all + keys.push_back( CipheredKey::fromBytes( keyBytes, false ) ); + } + + // Get data bytes + std::vector< uint8_t > data( bytes.begin() + offset, bytes.end() ); + + return Ciphertext( keys, data, _validate ); +} + +void Ciphertext::validate() const { + if ( keys.empty() || keys.size() > 2 ) + throw ThresholdUtils::IsNotWellFormed( "Ciphertext must contain exactly 1 or 2 keys" ); + + for ( const auto& key : keys ) { + key.validate(); + } + + if ( !data ) { + throw ThresholdUtils::IsNotWellFormed( "Cyphertext data is not initialized" ); + } + + // actual data without random secret must be at least 1 byte long + if ( data->size() <= RANDOM_SECRET_SIZE_BYTES ) { + throw ThresholdUtils::IsNotWellFormed( + "Cyphertext data is too short to hold random secret and at least 1 byte of data." ); + } +} + +const std::vector< CipheredKey >& Ciphertext::getKeys() const { + return keys; +} + +void Ciphertext::keepKey( size_t _idx ) { + if ( _idx >= keys.size() || keys.size() != 2 ) + throw ThresholdUtils::IncorrectInput( + "Key index is greater than number of the keys in ciphertext" ); + keys.erase( keys.begin() + ( keys.size() - _idx - 1 ) ); +} + +const CipheredKey& Ciphertext::getTargetKey() const { + if ( keys.size() != 1 ) + throw ThresholdUtils::IncorrectInput( "Cannot choose a target key" ); + return keys.front(); +} + +} // namespace libBLS diff --git a/threshold_encryption/Ciphertext.h b/threshold_encryption/Ciphertext.h new file mode 100644 index 00000000..7b0b7728 --- /dev/null +++ b/threshold_encryption/Ciphertext.h @@ -0,0 +1,95 @@ +#pragma once + +#include +#include + +#include + +#include "CipheredKey.h" + +namespace libBLS { + +constexpr size_t RANDOM_SECRET_SIZE_BYTES = MAX_FIELD_ELEMENT_SIZE_BYTES; + +/** + * @brief Holds the ciphered AES keys (vector), as well as the data + * ciphered with AES key. + */ +struct Ciphertext { + std::vector< CipheredKey > keys; + std::shared_ptr< std::vector< uint8_t > > data; + +public: + bool operator==( const Ciphertext& other ) const { + bool baseParams = keys == other.keys; + if ( data && other.data ) { + return baseParams && ( *data == *other.data ); + } + return baseParams; + } + + Ciphertext() = default; + + Ciphertext( const std::vector< CipheredKey >& _keys, const std::vector< uint8_t >& _data, + bool _validate = true ) + : keys( _keys ), data( std::make_shared< std::vector< uint8_t > >( _data ) ) { + if ( _validate ) + validate(); + } + + Ciphertext( + const CipheredKey& _key, const std::vector< uint8_t >& _data, bool _validate = true ) + : keys( { _key } ), data( std::make_shared< std::vector< uint8_t > >( _data ) ) { + if ( _validate ) + validate(); + } + + // Constructor for exactly two keys + Ciphertext( const CipheredKey& _key1, const CipheredKey& _key2, + const std::vector< uint8_t >& _data, bool _validate = true ) + : keys( { _key1, _key2 } ), data( std::make_shared< std::vector< uint8_t > >( _data ) ) { + if ( _validate ) + validate(); + } + + const std::vector< uint8_t >& getData() const; + + /** + * @brief Converts Ciphertext to bytes + * The byte format returned is: `[ num_keys | key1 | key2 | ... | data ]` + * + * where `data` can be of arbitrary size, each `key` is a fixed size, + * and `num_keys` is a 1-byte integer indicating the number of keys. + */ + const std::vector< uint8_t > toBytes() const; + + /** + * @brief Converts bytes to Ciphertext + */ + static Ciphertext fromBytes( std::vector< uint8_t >& bytes, bool _validate = true ); + + /** + * @brief Validates the Ciphertext + * @throw NotWellFormed if the it fails the validation + */ + void validate() const; + + /** + * @brief Returns the vector of keys in the Ciphertext + */ + const std::vector< CipheredKey >& getKeys() const; + + /** + * @brief keeps only one key for validation and decryption + * @param idx - index of the key to keep + */ + void keepKey( size_t _idx ); + + /** + * @brief get a CipheredKey for validation and decryption + * @throw IncorrectInput if there are 0 or 2 keys to choose + */ + const CipheredKey& getTargetKey() const; +}; + +} // namespace libBLS diff --git a/threshold_encryption/TEDecryptionShare.cpp b/threshold_encryption/TEDecryptionShare.cpp index 465f3aef..01d9c6aa 100644 --- a/threshold_encryption/TEDecryptionShare.cpp +++ b/threshold_encryption/TEDecryptionShare.cpp @@ -26,15 +26,21 @@ along with libBLS. If not, see . namespace libBLS { -TEDecryptionShare::TEDecryptionShare( const algebra::G2Point& _share, size_t _signerIndex ) +TEDecryptionShare::TEDecryptionShare( + const algebra::G2Point& _share, size_t _signerIndex, bool _validate ) : signerIndex( _signerIndex ), share( _share ) { - share.validate(); + if ( _validate ) { + share.validate(); + } } -TEDecryptionShare::TEDecryptionShare( const std::string& _hexaEncoded, size_t _signerIndex ) +TEDecryptionShare::TEDecryptionShare( + const std::string& _hexaEncoded, size_t _signerIndex, bool _validate ) : signerIndex( _signerIndex ) { share = algebra::G2Point::fromString( _hexaEncoded, Base::HEXA ); - share.validate(); + if ( _validate ) { + share.validate(); + } } size_t TEDecryptionShare::getSignerIndex() const { diff --git a/threshold_encryption/TEDecryptionShare.h b/threshold_encryption/TEDecryptionShare.h index e852e5d0..46dff9e3 100644 --- a/threshold_encryption/TEDecryptionShare.h +++ b/threshold_encryption/TEDecryptionShare.h @@ -40,18 +40,23 @@ class TEDecryptionShare { public: /** - * @param _signerIndex Index of the signer * @param _share Decryption share - * @note Validates that the share is well formed and non-zero. + * @param _signerIndex Index of the signer + * @param _validate Whether to validate the share or not (Optional, default: true) + * Note: Validation is a costly operation, so it can be skipped if the share is already known to + * be valid. */ - TEDecryptionShare( const algebra::G2Point& _share, size_t _signerIndex ); + TEDecryptionShare( const algebra::G2Point& _share, size_t _signerIndex, bool _validate = true ); /** - * @param _signerIndex Index of the signer * @param _hexaEncoded Hexa encoded string of the share - * @note Used when building from serialized decription share + * @param _signerIndex Index of the signer + * @param _validate Whether to validate the share or not (Optional, default: true) + * Note: Validation is a costly operation, so it can be skipped if the share is already known to + * be valid. */ - TEDecryptionShare( const std::string& _hexaEncoded, size_t _signerIndex ); + TEDecryptionShare( + const std::string& _hexaEncoded, size_t _signerIndex, bool _validate = true ); size_t getSignerIndex() const; diff --git a/threshold_encryption/ThresholdEncryption.cpp b/threshold_encryption/ThresholdEncryption.cpp index d81fe508..cdbf8ec0 100644 --- a/threshold_encryption/ThresholdEncryption.cpp +++ b/threshold_encryption/ThresholdEncryption.cpp @@ -46,15 +46,15 @@ std::vector< uint8_t > ThresholdEncryption::mockupEncrypt( std::copy( key.begin(), key.end(), mockupEncryptedKey.begin() ); - RandSecret random_secret{}; // Zero-initialized + RandSecret randomSecret{}; // Zero-initialized // Append random secret to end of message - std::vector< uint8_t > message_to_cipher( _message ); - message_to_cipher.insert( message_to_cipher.end(), random_secret.begin(), random_secret.end() ); + std::vector< uint8_t > messageToCipher( _message ); + messageToCipher.insert( messageToCipher.end(), randomSecret.begin(), randomSecret.end() ); // Cipher message + random secret using AES key AesGcmCipher aesGcmCipher{ key }; - auto encrypted_message = aesGcmCipher.encrypt( message_to_cipher ); + auto encryptedMessage = aesGcmCipher.encrypt( messageToCipher ); // 0x01 byte is needed for compatibility with real encryption // stands for the number of encrypted AES keys in the payload @@ -63,7 +63,7 @@ std::vector< uint8_t > ThresholdEncryption::mockupEncrypt( result.insert( result.end(), mockupEncryptedKey.begin(), mockupEncryptedKey.end() ); #pragma GCC diagnostic ignored "-Wstringop-overread" - result.insert( result.end(), encrypted_message.begin(), encrypted_message.end() ); + result.insert( result.end(), encryptedMessage.begin(), encryptedMessage.end() ); #pragma GCC diagnostic error "-Wstringop-overread" return result; @@ -102,14 +102,14 @@ std::vector< uint8_t > ThresholdEncryption::mockupDecrypt( } -Ciphertext ThresholdEncryption::encrypt( - const std::vector< uint8_t >& _message, const TEPublicKey& _commonPublic ) { - return encrypt( _message, std::vector< TEPublicKey >{ _commonPublic } ); +Ciphertext ThresholdEncryption::encrypt( const std::vector< uint8_t >& _message, + const TEPublicKey& _commonPublic, const EncryptMetaData& _metaData ) { + return encrypt( _message, std::vector< TEPublicKey >{ _commonPublic }, _metaData ); } -Ciphertext ThresholdEncryption::encrypt( - const std::vector< uint8_t >& _message, const std::vector< TEPublicKey >& _commonPublic ) { +Ciphertext ThresholdEncryption::encrypt( const std::vector< uint8_t >& _message, + const std::vector< TEPublicKey >& _commonPublic, const EncryptMetaData& _metaData ) { if ( _commonPublic.size() == 0 || _commonPublic.size() > 2 ) throw ThresholdUtils::IncorrectInput( "Must provide exactly 1 or 2 public keys for encryption" ); @@ -120,7 +120,7 @@ Ciphertext ThresholdEncryption::encrypt( rawPublicKeys.push_back( publicKey.getPublicKeyRaw() ); } - CipherResult cipher = TE::encryptWithAES( _message, rawPublicKeys ); + CipherResult cipher = TE::encryptWithAES( _message, rawPublicKeys, _metaData ); if ( !cipher.ciphertext ) { throw ThresholdUtils::IsNotWellFormed( "ciphertext is null" ); @@ -131,10 +131,11 @@ Ciphertext ThresholdEncryption::encrypt( return *cipher.ciphertext; } -void ThresholdEncryption::validateEncryption( const CipheredKey& _ciphertext ) { +void ThresholdEncryption::validateEncryption( + const CipheredKey& _ciphertext, const std::vector< uint8_t >* _associatedDataTE ) { const auto& [U, V, W] = _ciphertext; - algebra::G1Point H = TE::HashToGroup( U, V ); + algebra::G1Point H = TE::HashToGroup( U, V, _associatedDataTE ); // pairing( W, P ) == pairing( H, U ) bool validPairing = algebra::verifyPairingEq( W, algebra::G2Point::generator(), H, U ); @@ -145,11 +146,18 @@ void ThresholdEncryption::validateEncryption( const CipheredKey& _ciphertext ) { } std::vector< bool > ThresholdEncryption::validateEncryptionBatch( - const std::vector< CipheredKey >& _ciphertexts ) { + const std::vector< CipheredKey >& _ciphertexts, + const std::vector< std::vector< uint8_t > >* _associatedDataTE ) { if ( _ciphertexts.empty() ) { return {}; } + // Allow partial AAD: first N AADs apply to first N ciphertexts, rest have no AAD + if ( _associatedDataTE && _associatedDataTE->size() > _ciphertexts.size() ) { + throw ThresholdUtils::IncorrectInput( + "Associated data TE size cannot exceed ciphertexts size" ); + } + // Store concrete values (no reference_wrapper) static thread_local std::vector< algebra::G1Point > g1P1s; // W_i static thread_local std::vector< algebra::G1Point > g1P2s; // H_i @@ -165,7 +173,11 @@ std::vector< bool > ThresholdEncryption::validateEncryptionBatch( for ( size_t i = 0; i < size; ++i ) { const auto& [U, V, W] = _ciphertexts.at( i ); - const algebra::G1Point H = TE::HashToGroup( U, V ); + // Apply AAD only if provided and within AAD vector bounds + const std::vector< uint8_t >* aadPtr = + ( _associatedDataTE && i < _associatedDataTE->size() ) ? &_associatedDataTE->at( i ) : + nullptr; + const algebra::G1Point H = TE::HashToGroup( U, V, aadPtr ); g1P1s.at( i ) = W; g1P2s.at( i ) = H; @@ -178,16 +190,37 @@ std::vector< bool > ThresholdEncryption::validateEncryptionBatch( } std::vector< bool > ThresholdEncryption::validateEncryptionBatchParallel( - const std::vector< CipheredKey >& _ciphertexts ) { + const std::vector< CipheredKey >& _ciphertexts, + const std::vector< std::vector< uint8_t > >* _associatedDataTE ) { if ( _ciphertexts.empty() ) { return {}; } + // Allow partial AAD: first N AADs apply to first N ciphertexts, rest have no AAD + if ( _associatedDataTE && _associatedDataTE->size() > _ciphertexts.size() ) { + throw ThresholdUtils::IncorrectInput( + "Associated data TE size cannot exceed ciphertexts size" ); + } + auto result = ThresholdUtils::executeInParallel< bool >( - _ciphertexts.size(), [&_ciphertexts]( size_t startIdx, size_t endIdx ) { + _ciphertexts.size(), [&_ciphertexts, _associatedDataTE]( size_t startIdx, size_t endIdx ) { const std::vector< CipheredKey > subset( _ciphertexts.begin() + startIdx, _ciphertexts.begin() + endIdx ); - return ThresholdEncryption::validateEncryptionBatch( subset ); + // Slice AAD vector for this subset - handle partial AAD + const std::vector< std::vector< uint8_t > >* aadSubset = nullptr; + std::vector< std::vector< uint8_t > > aadSubsetStorage; + if ( _associatedDataTE ) { + // Only include AAD entries that are within bounds + size_t aadSize = _associatedDataTE->size(); + size_t aadStart = std::min( startIdx, aadSize ); + size_t aadEnd = std::min( endIdx, aadSize ); + if ( aadStart < aadEnd ) { + aadSubsetStorage.assign( _associatedDataTE->begin() + aadStart, + _associatedDataTE->begin() + aadEnd ); + aadSubset = &aadSubsetStorage; + } + } + return ThresholdEncryption::validateEncryptionBatch( subset, aadSubset ); } ); return result; @@ -195,19 +228,20 @@ std::vector< bool > ThresholdEncryption::validateEncryptionBatchParallel( TEDecryptionShare ThresholdEncryption::partialDecrypt( const CipheredKey& _ciphertext, const TEPrivateKeyShare& _pkeyShare ) { - algebra::G2Point decryption_share = _pkeyShare.getPrivateKeyRaw() * _ciphertext.U; + algebra::G2Point decryptionShare = _pkeyShare.getPrivateKeyRaw() * _ciphertext.U; // no need to validate G2Point - if both inputs are already valid, multiplication // always produces a valid point - TEDecryptionShare share( decryption_share, _pkeyShare.getSignerIndex() ); + TEDecryptionShare share( decryptionShare, _pkeyShare.getSignerIndex() ); return share; } void ThresholdEncryption::validateDecryptionShare( const CipheredKey& _cipherText, - const TEDecryptionShare& _decryptionShare, const TEPublicKeyShare& _publicKey ) { - if ( !TE::Verify( - _cipherText, _decryptionShare.getShareRaw(), _publicKey.getPublicKeyRaw() ) ) { + const TEDecryptionShare& _decryptionShare, const TEPublicKeyShare& _publicKey, + const std::vector< uint8_t >* _associatedDataTE ) { + if ( !TE::Verify( _cipherText, _decryptionShare.getShareRaw(), _publicKey.getPublicKeyRaw(), + _associatedDataTE ) ) { throw ThresholdUtils::IsNotWellFormed( "Invalid decryption share" ); } } @@ -215,7 +249,8 @@ void ThresholdEncryption::validateDecryptionShare( const CipheredKey& _cipherTex std::vector< bool > ThresholdEncryption::validateDecryptionSharesBatch( const std::vector< CipheredKey >& _cipherTexts, const std::vector< TEDecryptionShare >& _decryptionShares, - const std::vector< TEPublicKeyShare >& _publicKeys ) { + const std::vector< TEPublicKeyShare >& _publicKeys, + const std::vector< std::vector< uint8_t > >* _associatedDataTE ) { if ( _cipherTexts.empty() ) { return {}; } @@ -231,13 +266,14 @@ std::vector< bool > ThresholdEncryption::validateDecryptionSharesBatch( publicKeysRaw.push_back( key.getPublicKeyRaw() ); } - return TE::VerifyBatch( _cipherTexts, decryptionSharesRaw, publicKeysRaw ); + return TE::VerifyBatch( _cipherTexts, decryptionSharesRaw, publicKeysRaw, _associatedDataTE ); } std::vector< bool > ThresholdEncryption::validateDecryptionSharesBatchParallel( const std::vector< CipheredKey >& _cipherTexts, const std::vector< TEDecryptionShare >& _decryptionShares, - const std::vector< TEPublicKeyShare >& _publicKeys ) { + const std::vector< TEPublicKeyShare >& _publicKeys, + const std::vector< std::vector< uint8_t > >* _associatedDataTE ) { if ( _cipherTexts.empty() ) { return {}; } @@ -255,11 +291,17 @@ std::vector< bool > ThresholdEncryption::validateDecryptionSharesBatchParallel( "Decryption shares size must be multiple of ciphertexts size" ); } + // Allow partial AAD: first N AADs apply to first N ciphertexts, rest have no AAD + if ( _associatedDataTE && _associatedDataTE->size() > _cipherTexts.size() ) { + throw ThresholdUtils::IncorrectInput( + "Associated data size cannot exceed number of ciphertexts" ); + } + size_t sharesPerCiphertext = _decryptionShares.size() / _cipherTexts.size(); - auto result = libBLS::ThresholdUtils::executeInParallel< bool >( - _cipherTexts.size(), [sharesPerCiphertext, &_cipherTexts, &_decryptionShares, &_publicKeys]( - size_t startIdx, size_t endIdx ) { + auto result = libBLS::ThresholdUtils::executeInParallel< bool >( _cipherTexts.size(), + [sharesPerCiphertext, &_cipherTexts, &_decryptionShares, &_publicKeys, _associatedDataTE]( + size_t startIdx, size_t endIdx ) { const std::vector< CipheredKey > ciphertexts( _cipherTexts.begin() + startIdx, _cipherTexts.begin() + endIdx ); const std::vector< TEDecryptionShare > decryptionShares( @@ -269,8 +311,23 @@ std::vector< bool > ThresholdEncryption::validateDecryptionSharesBatchParallel( _publicKeys.begin() + startIdx * sharesPerCiphertext, _publicKeys.begin() + endIdx * sharesPerCiphertext ); + // Slice AAD for this subset - handle partial AAD + const std::vector< std::vector< uint8_t > >* aadSubset = nullptr; + std::vector< std::vector< uint8_t > > aadSubsetStorage; + if ( _associatedDataTE ) { + // Only include AAD entries that are within bounds + size_t aadSize = _associatedDataTE->size(); + size_t aadStart = std::min( startIdx, aadSize ); + size_t aadEnd = std::min( endIdx, aadSize ); + if ( aadStart < aadEnd ) { + aadSubsetStorage.assign( _associatedDataTE->begin() + aadStart, + _associatedDataTE->begin() + aadEnd ); + aadSubset = &aadSubsetStorage; + } + } + return ThresholdEncryption::validateDecryptionSharesBatch( - ciphertexts, decryptionShares, publicKeys ); + ciphertexts, decryptionShares, publicKeys, aadSubset ); } ); return result; } @@ -360,10 +417,12 @@ std::vector< std::optional< AES256Key > > ThresholdEncryption::combineSharesBatc return result; } -void ThresholdEncryption::validateCombinedDecryption( - const Ciphertext& _ciphertext, const AES256Key& _aesKey, const TEPublicKey& _publicKey ) { +void ThresholdEncryption::validateCombinedDecryption( const Ciphertext& _ciphertext, + const AES256Key& _aesKey, const TEPublicKey& _publicKey, + const std::optional< std::vector< uint8_t > >& _associatedData ) { // decipher & validate plaintext - std::vector< uint8_t > decipheredMessage = decipherAESAndValidate( _ciphertext, _aesKey ); + std::vector< uint8_t > decipheredMessage = + decipherAESAndValidate( _ciphertext, _aesKey, _associatedData ); validateDecipheredMessage( decipheredMessage, _ciphertext, _aesKey, _publicKey ); } @@ -433,20 +492,22 @@ std::vector< bool > ThresholdEncryption::validateCombinedDecryptionBatchParallel } -std::vector< uint8_t > ThresholdEncryption::decrypt( - const Ciphertext& _ciphertext, const AES256Key& _aesKey ) { +std::vector< uint8_t > ThresholdEncryption::decrypt( const Ciphertext& _ciphertext, + const AES256Key& _aesKey, const std::optional< std::vector< uint8_t > >& _associatedData ) { // decipher & validate plaintext - std::vector< uint8_t > data = decipherAESAndValidate( _ciphertext, _aesKey ); + std::vector< uint8_t > data = decipherAESAndValidate( _ciphertext, _aesKey, _associatedData ); // safe - size of decipheredMessage was already validated data.resize( data.size() - RANDOM_SECRET_SIZE_BYTES ); return data; } -std::vector< uint8_t > ThresholdEncryption::validateAndDecrypt( - const Ciphertext& _ciphertext, const AES256Key& _aesKey, const TEPublicKey& _publicKey ) { +std::vector< uint8_t > ThresholdEncryption::validateAndDecrypt( const Ciphertext& _ciphertext, + const AES256Key& _aesKey, const TEPublicKey& _publicKey, + const std::optional< std::vector< uint8_t > >& _associatedData ) { // decipher & validate plaintext - std::vector< uint8_t > decipheredMessage = decipherAESAndValidate( _ciphertext, _aesKey ); + std::vector< uint8_t > decipheredMessage = + decipherAESAndValidate( _ciphertext, _aesKey, _associatedData ); validateDecipheredMessage( decipheredMessage, _ciphertext, _aesKey, _publicKey ); @@ -490,10 +551,10 @@ void ThresholdEncryption::validateDecipheredMessage( } } -std::vector< uint8_t > ThresholdEncryption::decipherAESAndValidate( - const Ciphertext& _ciphertext, const AES256Key& key ) { - AesGcmCipher aesGcmCipher{ key }; - std::vector< uint8_t > data = aesGcmCipher.decrypt( _ciphertext.getData() ); +std::vector< uint8_t > ThresholdEncryption::decipherAESAndValidate( const Ciphertext& _ciphertext, + const AES256Key& _key, const std::optional< std::vector< uint8_t > >& _associatedData ) { + AesGcmCipher aesGcmCipher{ _key }; + std::vector< uint8_t > data = aesGcmCipher.decrypt( _ciphertext.getData(), _associatedData ); // validate output size_t cipherSize = _ciphertext.getData().size(); diff --git a/threshold_encryption/ThresholdEncryption.h b/threshold_encryption/ThresholdEncryption.h index 1996dfe8..e1d23dd2 100644 --- a/threshold_encryption/ThresholdEncryption.h +++ b/threshold_encryption/ThresholdEncryption.h @@ -24,12 +24,14 @@ #ifndef LIBBLS_THRESHOLDENCRYPTION_H #define LIBBLS_THRESHOLDENCRYPTION_H +#include "AesGcmCipher.h" #include "TEDecryptSet.h" #include "TEPrivateKeyShare.h" #include "TEPublicKeyShare.h" #include "threshold_encryption.h" #include #include +#include namespace libBLS { @@ -56,38 +58,50 @@ class ThresholdEncryption { * Public key is validated on constructor. Thus it is assumed to be always valid. * @return Ciphertext - Struct containing TE(AESKey) and AESKey(message) */ - static Ciphertext encrypt( - const std::vector< uint8_t >& _message, const TEPublicKey& _commonPublic ); - static Ciphertext encrypt( - const std::vector< uint8_t >& _message, const std::vector< TEPublicKey >& _commonPublic ); + static Ciphertext encrypt( const std::vector< uint8_t >& _message, + const TEPublicKey& _commonPublic, const EncryptMetaData& _metaData = EncryptMetaData() ); + + static Ciphertext encrypt( const std::vector< uint8_t >& _message, + const std::vector< TEPublicKey >& _commonPublic, + const EncryptMetaData& _metaData = EncryptMetaData() ); /** - * @brief Validates the TE ciphered key. SIngle threaded. + * @brief Validates the TE ciphered key. Single threaded. * * @param _cipheredKey The encrypted AESKey used to encrypt the message held by Ciphertext * struct + * @param _associatedDataTE Optional associated data used during encryption * @throws Exceptions in case validation fails */ - static void validateEncryption( const CipheredKey& _cipheredKey ); + static void validateEncryption( const CipheredKey& _cipheredKey, + const std::vector< uint8_t >* _associatedDataTE = nullptr ); /** * @brief Validates a batch of TE ciphered keys. Same as above, but more performant * @param _ciphertexts Vector of encrypted AESKeys used to encrypt the message held by * Ciphertext struct + * @param _associatedDataTE Optional vector of associated data. Supports partial AAD: + * if size < ciphertexts size, first N AADs apply to first N ciphertexts, remaining + * ciphertexts are validated without AAD. Empty AAD entries are treated as no AAD. * @return Vec of bools, each idx specifying if it was successfully validated or not. * Each idx corresponds to the same idx on _ciphertexts. */ static std::vector< bool > validateEncryptionBatch( - const std::vector< CipheredKey >& _ciphertexts ); + const std::vector< CipheredKey >& _ciphertexts, + const std::vector< std::vector< uint8_t > >* _associatedDataTE = nullptr ); /** * @brief Validates a batch of TE ciphered keys in parallel. Same as above, but multi-threaded * @param _ciphertexts Vector of encrypted AESKeys used to encrypt the message held by * Ciphertext struct + * @param _associatedDataTE Optional vector of associated data. Supports partial AAD: + * if size < ciphertexts size, first N AADs apply to first N ciphertexts, remaining + * ciphertexts are validated without AAD. Empty AAD entries are treated as no AAD. * @return Vec of bools, each idx specifying if it was successfully validated or not */ static std::vector< bool > validateEncryptionBatchParallel( - const std::vector< CipheredKey >& _ciphertexts ); + const std::vector< CipheredKey >& _ciphertexts, + const std::vector< std::vector< uint8_t > >* _associatedDataTE = nullptr ); /** * @brief Generates a decryption share for the given ciphered key @@ -107,10 +121,12 @@ class ThresholdEncryption { * @param _cipheredKey The encrypted AESKey used to encrypt the message held by Ciphertext * struct * @param decryption_share The decryption share + * @param _associatedDataTE Optional TE AAD used during encryption * @throws Exception if the decryption share is not valid */ static void validateDecryptionShare( const CipheredKey& _cipheredKey, - const TEDecryptionShare& _decryptionShare, const TEPublicKeyShare& _publicKey ); + const TEDecryptionShare& _decryptionShare, const TEPublicKeyShare& _publicKey, + const std::vector< uint8_t >* _associatedDataTE = nullptr ); /** @@ -123,13 +139,17 @@ class ThresholdEncryption { * `TEPublicKeyShare` each. * @param _decryptionShares Vector of decryption shares. Must be same size as _publicKeys. * @param _publicKeys Vector of public keys corresponding to the decryption shares. + * @param _associatedDataTE Optional vector of associated data. Supports partial AAD: + * if size < ciphertexts size, first N AADs apply to first N ciphertexts, remaining + * ciphertexts are validated without AAD. Empty AAD entries are treated as no AAD. * @return Vec of bools, each idx specifying if it was successfully validated or not. * Each idx corresponds to the same idx on _decryptionShares and _publicKeys. */ static std::vector< bool > validateDecryptionSharesBatch( const std::vector< CipheredKey >& _cipheredKeys, const std::vector< TEDecryptionShare >& _decryptionShares, - const std::vector< TEPublicKeyShare >& _publicKeys ); + const std::vector< TEPublicKeyShare >& _publicKeys, + const std::vector< std::vector< uint8_t > >* _associatedDataTE = nullptr ); /** * @brief Validates a batch of decryption shares in parallel - same as above, but multi-threaded @@ -137,7 +157,8 @@ class ThresholdEncryption { static std::vector< bool > validateDecryptionSharesBatchParallel( const std::vector< CipheredKey >& _cipherTexts, const std::vector< TEDecryptionShare >& _decryptionShares, - const std::vector< TEPublicKeyShare >& _publicKeys ); + const std::vector< TEPublicKeyShare >& _publicKeys, + const std::vector< std::vector< uint8_t > >* _associatedDataTE = nullptr ); /** * @brief Combines decryption shares to reconstruct the original AES key. @@ -177,8 +198,9 @@ class ThresholdEncryption { * @throws runtime_exception If the decryption using the AES fails (should only happen if the * key is tampered) */ - static void validateCombinedDecryption( - const Ciphertext& _ciphertext, const AES256Key& _aesKey, const TEPublicKey& _publicKey ); + static void validateCombinedDecryption( const Ciphertext& _ciphertext, const AES256Key& _aesKey, + const TEPublicKey& _publicKey, + const std::optional< std::vector< uint8_t > >& _associatedData = std::nullopt ); static std::vector< bool > validateCombinedDecryptionBatch( const std::vector< Ciphertext >& _ciphertexts, const std::vector< AES256Key >& _aesKeys, @@ -198,18 +220,20 @@ class ThresholdEncryption { * * @param cyphertext The encrypted message * @param aesKey The AES key + * @param associatedData The associated data used for encryption * @return std::vector The decrypted message apart from the random secret */ - static std::vector< uint8_t > decrypt( - const Ciphertext& _ciphertext, const AES256Key& _aesKey ); + static std::vector< uint8_t > decrypt( const Ciphertext& _ciphertext, const AES256Key& _aesKey, + const std::optional< std::vector< uint8_t > >& _associatedData = std::nullopt ); /** * @brief Validates the cyphertext and decrypts the message * Same as calling `validateCombinedDecryption` and `decrypt` in sequence, * but avoids deciphering twice - more performant alternative */ - static std::vector< uint8_t > validateAndDecrypt( - const Ciphertext& _ciphertext, const AES256Key& _aesKey, const TEPublicKey& _publicKey ); + static std::vector< uint8_t > validateAndDecrypt( const Ciphertext& _ciphertext, + const AES256Key& _aesKey, const TEPublicKey& _publicKey, + const std::optional< std::vector< uint8_t > >& _associatedData = std::nullopt ); /** * @brief validates _aesKey against the one stored in _cyphertext @@ -228,9 +252,9 @@ class ThresholdEncryption { static inline RandSecret extractRandomSecretFromMessage( const std::vector< uint8_t >& _message ) { - size_t msg_length = _message.size(); + size_t msgLength = _message.size(); - if ( msg_length < RANDOM_SECRET_SIZE_BYTES ) { + if ( msgLength < RANDOM_SECRET_SIZE_BYTES ) { throw ThresholdUtils::IncorrectInput( "Message is too short" ); } @@ -247,8 +271,9 @@ class ThresholdEncryption { * * Helper function */ - static std::vector< uint8_t > decipherAESAndValidate( - const Ciphertext& _ciphertext, const AES256Key& key ); + static std::vector< uint8_t > decipherAESAndValidate( const Ciphertext& _ciphertext, + const AES256Key& key, + const std::optional< std::vector< uint8_t > >& _associatedData = std::nullopt ); }; } // namespace libBLS diff --git a/threshold_encryption/encryptMessage.cpp b/threshold_encryption/encryptMessage.cpp index 75d1bd21..bd8f6bfb 100644 --- a/threshold_encryption/encryptMessage.cpp +++ b/threshold_encryption/encryptMessage.cpp @@ -27,21 +27,38 @@ along with libBLS. If not, see . extern "C" { -const char* encryptMessage( const char* data, const char* key ) { - static std::string cipheredMessageStr; +const char* encryptMessage( const char* data, const char* key, + const char* additionalAuthenticatedDataTE, const char* additionalAuthenticatedDataAES ) { + thread_local std::string cipheredMessageStr; libBLS::init(); - // convert from char into vec of bytes + // convert from char into vector of bytes std::vector< uint8_t > messageBytes = libBLS::ThresholdUtils::hexCStringToBytes( data ); // build public key std::string keyStr( key ); libBLS::TEPublicKey commonPublic( keyStr, libBLS::Base::HEXA ); + // build encrypt meta data + libBLS::EncryptMetaData metaData; + + // set AAD AES if not empty + if ( additionalAuthenticatedDataAES != nullptr && additionalAuthenticatedDataAES[0] != '\0' ) { + metaData.associatedDataAesGcm = + libBLS::ThresholdUtils::hexCStringToBytes( additionalAuthenticatedDataAES ); + } + + // set AAD TE if not empty + if ( additionalAuthenticatedDataTE != nullptr && additionalAuthenticatedDataTE[0] != '\0' ) { + metaData.associatedDataTE = + libBLS::ThresholdUtils::hexCStringToBytes( additionalAuthenticatedDataTE ); + } + // encrypt message libBLS::Ciphertext cipheredMessage = - libBLS::ThresholdEncryption::encrypt( messageBytes, commonPublic ); + libBLS::ThresholdEncryption::encrypt( messageBytes, commonPublic, metaData ); + std::vector< uint8_t > cipheredMessageBytes = cipheredMessage.toBytes(); cipheredMessageStr = libBLS::ThresholdUtils::bytesToHexString( cipheredMessageBytes ); @@ -49,8 +66,9 @@ const char* encryptMessage( const char* data, const char* key ) { return cipheredMessageStr.c_str(); } -const char* encryptMessageDualKey( const char* data, const char* firstKey, const char* secondKey ) { - static std::string cipheredMessageStr; +const char* encryptMessageDualKey( const char* data, const char* firstKey, const char* secondKey, + const char* additionalAuthenticatedDataTE, const char* additionalAuthenticatedDataAES ) { + thread_local std::string cipheredMessageStr; libBLS::init(); @@ -66,9 +84,24 @@ const char* encryptMessageDualKey( const char* data, const char* firstKey, const commonPublicKeys.push_back( firstCommonPublic ); commonPublicKeys.push_back( secondCommonPublic ); - // encrypt message + // Build meta data + libBLS::EncryptMetaData metaData; + + // set AAD AES if not empty + if ( additionalAuthenticatedDataAES != nullptr && additionalAuthenticatedDataAES[0] != '\0' ) { + metaData.associatedDataAesGcm = + libBLS::ThresholdUtils::hexCStringToBytes( additionalAuthenticatedDataAES ); + } + + // set AAD TE if not empty + if ( additionalAuthenticatedDataTE != nullptr && additionalAuthenticatedDataTE[0] != '\0' ) { + metaData.associatedDataTE = + libBLS::ThresholdUtils::hexCStringToBytes( additionalAuthenticatedDataTE ); + } + libBLS::Ciphertext cipheredMessage = - libBLS::ThresholdEncryption::encrypt( messageBytes, commonPublicKeys ); + libBLS::ThresholdEncryption::encrypt( messageBytes, commonPublicKeys, metaData ); + std::vector< uint8_t > cipheredMessageBytes = cipheredMessage.toBytes(); cipheredMessageStr = libBLS::ThresholdUtils::bytesToHexString( cipheredMessageBytes ); @@ -77,11 +110,11 @@ const char* encryptMessageDualKey( const char* data, const char* firstKey, const } const char* encryptMessageMockup( const char* data ) { - static std::string cipheredMessageStr; + thread_local std::string cipheredMessageStr; libBLS::init(); - // convert from char into vec of bytes + // convert from char into vector of bytes std::vector< uint8_t > messageBytes = libBLS::ThresholdUtils::hexCStringToBytes( data ); // encrypt message diff --git a/threshold_encryption/threshold_encryption.cpp b/threshold_encryption/threshold_encryption.cpp index e28c4cc1..a970a5d7 100644 --- a/threshold_encryption/threshold_encryption.cpp +++ b/threshold_encryption/threshold_encryption.cpp @@ -55,45 +55,89 @@ std::string TE::Hash( const algebra::G2Point& Y ) { return sha256hex; } -algebra::G1Point TE::HashToGroup( const algebra::G2Point& U, const AES256Key& V ) { +algebra::G1Point TE::HashToGroup( + const algebra::G2Point& U, const AES256Key& V, const std::vector< uint8_t >* associatedData ) { // assumed that U lies in G2 - auto U_str = U.toStringArray( Base::DEC ); - std::string V_str = ThresholdUtils::bytesToHexString( V ); + auto uStr = U.toStringArray( Base::DEC ); + std::string vStr = ThresholdUtils::bytesToHexString( V ); + + // Build hash input: U coordinates + V + optional AAD + std::string hashInput = uStr[0] + uStr[1] + uStr[2] + uStr[3] + vStr; + + if ( associatedData && !associatedData->empty() ) { + std::string aadStr = ThresholdUtils::bytesToHexString( *associatedData ); + hashInput += aadStr; + } // hash 2x - const std::string sha256hex = - ThresholdUtils::sha256( U_str[0] + U_str[1] + U_str[2] + U_str[3] + V_str ); - std::string hash_str = ThresholdUtils::sha256( sha256hex ); + const std::string sha256hex = ThresholdUtils::sha256( hashInput ); + std::string hashStr = ThresholdUtils::sha256( sha256hex ); - std::vector< uint8_t > bytes = ThresholdUtils::hexCStringToBytes( hash_str.c_str() ); + std::vector< uint8_t > bytes = ThresholdUtils::hexCStringToBytes( hashStr.c_str() ); // copy first 32 bytes - auto hash_bytes_arr = std::array< uint8_t, algebra::MAX_FIELD_ELEMENT_SIZE_BYTES >(); + auto hashBytesArr = std::array< uint8_t, algebra::MAX_FIELD_ELEMENT_SIZE_BYTES >(); std::copy( bytes.begin(), bytes.begin() + algebra::MAX_FIELD_ELEMENT_SIZE_BYTES, - hash_bytes_arr.begin() ); + hashBytesArr.begin() ); - return algebra::G1Point::fromHash( hash_bytes_arr ); + return algebra::G1Point::fromHash( hashBytesArr ); } -CipheredKeyResult TE::getCiphertext( const AES256Key& key, const algebra::G2Point& commonPublic ) { - return getCiphertext( key, std::vector< algebra::G2Point >{ commonPublic } ); +CipheredKeyResult TE::getCiphertext( const AES256Key& key, const algebra::G2Point& commonPublic, + const std::optional< std::vector< uint8_t > >& associatedDataTE, + const std::optional< Seed256 >& seed ) { + return getCiphertext( + key, std::vector< algebra::G2Point >{ commonPublic }, associatedDataTE, seed ); } -CipheredKeyResult TE::getCiphertext( - const AES256Key& key, const std::vector< algebra::G2Point >& commonPublicVector ) { +CipheredKeyResult TE::getCiphertext( const AES256Key& key, + const std::vector< algebra::G2Point >& commonPublicVector, + const std::optional< std::vector< uint8_t > >& associatedDataTE, + const std::optional< Seed256 >& seed ) { algebra::FrScalar r = algebra::FrScalar::random(); - while ( r.isZero() ) { + // set first value for r scalar + if ( seed.has_value() ) { + // Derive scalar r using SHA256 with domain separation + // derivation: SHA256( seed || "Scalar" ) + std::vector< uint8_t > input( seed->data.begin(), seed->data.end() ); + const std::string domain = "Scalar"; + input.insert( input.end(), domain.begin(), domain.end() ); + + std::string inputStr( input.begin(), input.end() ); + std::string hashHex = ThresholdUtils::sha256( inputStr ); + std::vector< uint8_t > derivedScalar = ThresholdUtils::hexCStringToBytes( hashHex.c_str() ); + + // This maps the 32-byte hash into the scalar field (handling modulo prime order etc) + r = algebra::FrScalar::fromHashBytes( derivedScalar.data(), derivedScalar.size() ); + } else { r = algebra::FrScalar::random(); } + // make sure it is different from zero + while ( r.isZero() ) { + if ( seed.has_value() ) { + // Very unlikely case where the derived scalar is zero + // We can re-hash the derived scalar to get a new one + std::string hashHex = ThresholdUtils::sha256( r.toString( Base::HEXA ) ); + std::vector< uint8_t > derivedScalar = + ThresholdUtils::hexCStringToBytes( hashHex.c_str() ); + r = algebra::FrScalar::fromHashBytes( derivedScalar.data(), derivedScalar.size() ); + } else { + r = algebra::FrScalar::random(); + } + } + std::vector< CipheredKey > cipheredKeys; algebra::G2Point U = r * algebra::G2Point::generator(); // convert to affine coordinate here to avoid doing it twice inside the loop U.toAffineCoordinates(); + + const auto aadPtr = associatedDataTE.has_value() ? &associatedDataTE.value() : nullptr; + for ( const auto& commonPublic : commonPublicVector ) { algebra::G2Point Y; Y = r * commonPublic; @@ -110,19 +154,19 @@ CipheredKeyResult TE::getCiphertext( V[i] = key[i] ^ static_cast< uint8_t >( hash[i] ); } - std::string v_str = ThresholdUtils::bytesToHexString( V ); + std::string vStr = ThresholdUtils::bytesToHexString( V ); algebra::G1Point W, H; - H = HashToGroup( U, V ); + H = HashToGroup( U, V, aadPtr ); W = r * H; cipheredKeys.emplace_back( U, V, W ); } - RandSecret random_secret = r.toByteArray(); + RandSecret randomSecret = r.toByteArray(); - return { cipheredKeys, std::move( random_secret ) }; + return { cipheredKeys, std::move( randomSecret ) }; } /** @@ -144,34 +188,42 @@ CipheredKeyResult TE::getCiphertext( * * @note Initializes AES before encryption */ -CipherResult TE::encryptWithAES( - const std::vector< uint8_t >& message, const algebra::G2Point& commonPublic ) { - return encryptWithAES( message, std::vector< algebra::G2Point >{ commonPublic } ); +CipherResult TE::encryptWithAES( const std::vector< uint8_t >& message, + const algebra::G2Point& commonPublic, const EncryptMetaData& metaData ) { + return encryptWithAES( message, std::vector< algebra::G2Point >{ commonPublic }, metaData ); } -CipherResult TE::encryptWithAES( - const std::vector< uint8_t >& message, const std::vector< algebra::G2Point >& commonPublic ) { - // create random AES key - AES256Key key; - if ( RAND_bytes( key.data(), key.size() ) != 1 ) { - throw ThresholdUtils::IsNotWellFormed( "Failed to generate random key" ); +CipherResult TE::encryptWithAES( const std::vector< uint8_t >& message, + const std::vector< algebra::G2Point >& commonPublic, const EncryptMetaData& metaData ) { + // Create AesGcmCipher - delegates key generation/derivation to the class + // If seed is provided, key is derived deterministically; otherwise random + std::unique_ptr< AesGcmCipher > aesGcmCipher; + if ( metaData.seed.has_value() ) { + aesGcmCipher = std::make_unique< AesGcmCipher >( metaData.seed.value() ); + } else { + aesGcmCipher = std::make_unique< AesGcmCipher >(); } - // cipher aes key - CipheredKeyResult result = getCiphertext( key, commonPublic ); + + // Get the key from cipher for threshold encryption + const AES256Key& key = aesGcmCipher->getKey(); + + // cipher aes key (with optional TE AAD) + + CipheredKeyResult result = + getCiphertext( key, commonPublic, metaData.associatedDataTE, metaData.seed ); // append random secret to end of message - std::vector< uint8_t > message_to_cipher( message ); - message_to_cipher.insert( - message_to_cipher.end(), result.random_secret.begin(), result.random_secret.end() ); + std::vector< uint8_t > messageToCipher( message ); + messageToCipher.insert( + messageToCipher.end(), result.randomSecret.begin(), result.randomSecret.end() ); - // cipher message + random secret using AES key - AesGcmCipher aesGcmCipher{ key }; - auto encrypted_message = aesGcmCipher.encrypt( message_to_cipher ); + // cipher message + random secret using AES key (with optional AES AAD) + auto encryptedMessage = aesGcmCipher->encrypt( messageToCipher, metaData.associatedDataAesGcm ); std::shared_ptr< Ciphertext > ciphertext = - std::make_shared< Ciphertext >( result.ciphertext, encrypted_message ); + std::make_shared< Ciphertext >( result.ciphertext, encryptedMessage ); - return { ciphertext, result.random_secret }; + return { ciphertext, result.randomSecret }; } @@ -206,7 +258,7 @@ std::pair< std::string, RandSecret > TE::encryptMessage( std::vector< uint8_t > ciphertextBytes = ciphertext.ciphertext->toBytes(); std::string ciphertextHexa = ThresholdUtils::bytesToHexString( ciphertextBytes ); - return std::make_pair( ciphertextHexa, ciphertext.random_secret ); + return std::make_pair( ciphertextHexa, ciphertext.randomSecret ); } @@ -225,9 +277,9 @@ std::pair< std::string, RandSecret > TE::encryptMessage( * @param secret_key The secret key share (element of Fr) used for decryption */ algebra::G2Point TE::getDecryptionShare( - const CipheredKey& ciphertext, const algebra::FrScalar& secret_key ) { - algebra::G2Point ret_val = secret_key * ciphertext.U; - return ret_val; + const CipheredKey& ciphertext, const algebra::FrScalar& secretKey ) { + algebra::G2Point retVal = secretKey * ciphertext.U; + return retVal; } /** @@ -248,14 +300,14 @@ algebra::G2Point TE::getDecryptionShare( * @return false if either the ciphertext is invalid or the decryption share verification fails */ bool TE::Verify( const CipheredKey& ciphertext, const algebra::G2Point& decryptionShare, - const algebra::G2Point& public_key ) { + const algebra::G2Point& publicKey, const std::vector< uint8_t >* associatedDataTE ) { auto [U, V, W] = ciphertext; - algebra::G1Point H = HashToGroup( U, V ); + algebra::G1Point H = HashToGroup( U, V, associatedDataTE ); // no need to validate ciphertext's pairing - assumed to be validated already via // `validateEncryption` call - bool isSecondPairingValid = algebra::verifyPairingEq( W, public_key, H, decryptionShare ); + bool isSecondPairingValid = algebra::verifyPairingEq( W, publicKey, H, decryptionShare ); return isSecondPairingValid; } @@ -280,7 +332,8 @@ bool TE::Verify( const CipheredKey& ciphertext, const algebra::G2Point& decrypti */ std::vector< bool > TE::VerifyBatch( const std::vector< CipheredKey >& ciphertexts, const std::vector< algebra::G2Point >& decryptionShares, - const std::vector< algebra::G2Point >& publicKeys ) { + const std::vector< algebra::G2Point >& publicKeys, + const std::vector< std::vector< uint8_t > >* associatedDataTE ) { const size_t size = decryptionShares.size(); const size_t numberOfBatches = ciphertexts.size(); @@ -298,15 +351,25 @@ std::vector< bool > TE::VerifyBatch( const std::vector< CipheredKey >& ciphertex "decryption shares and public keys must have same size" ); } + // Allow partial AAD: first N AADs apply to first N ciphertexts, rest have no AAD + if ( associatedDataTE && associatedDataTE->size() > numberOfBatches ) { + throw ThresholdUtils::IncorrectInput( + "associated data size cannot exceed number of ciphertexts" ); + } + std::vector< algebra::G1Point > g1P1s; std::vector< algebra::G1Point > g1P2s; g1P1s.reserve( ciphertexts.size() ); g1P2s.reserve( ciphertexts.size() ); - for ( const auto& cipher : ciphertexts ) { - const auto& [U, V, W] = cipher; + for ( size_t i = 0; i < ciphertexts.size(); ++i ) { + const auto& [U, V, W] = ciphertexts[i]; + // Apply AAD only if provided and within AAD vector bounds + const std::vector< uint8_t >* aadPtr = + ( associatedDataTE && i < associatedDataTE->size() ) ? &associatedDataTE->at( i ) : + nullptr; - algebra::G1Point H = HashToGroup( U, V ); + algebra::G1Point H = HashToGroup( U, V, aadPtr ); // no need to validate H - assumes H has been validated already when performing the // ciphertext validation at the start of TE process @@ -378,11 +441,11 @@ AES256Key TE::CombineSharesIntoAESKey( idx[i] = decryptionShares[i].second; } - std::vector< std::reference_wrapper< const algebra::G2Point > > shares_ref; + std::vector< std::reference_wrapper< const algebra::G2Point > > sharesRef; for ( size_t i = 0; i < this->t_; ++i ) { - shares_ref.emplace_back( std::cref( decryptionShares[i].first ) ); + sharesRef.emplace_back( std::cref( decryptionShares[i].first ) ); } - algebra::G2Point rebuiltG2 = algebra::lagrangeInterpolateAt0( idx, this->t_, shares_ref ); + algebra::G2Point rebuiltG2 = algebra::lagrangeInterpolateAt0( idx, this->t_, sharesRef ); std::string hash = this->Hash( rebuiltG2 ); diff --git a/threshold_encryption/threshold_encryption.h b/threshold_encryption/threshold_encryption.h index d91e5c7e..e810e029 100644 --- a/threshold_encryption/threshold_encryption.h +++ b/threshold_encryption/threshold_encryption.h @@ -25,348 +25,58 @@ along with libBLS. If not, see . #include #include +#include #include -#include #include #include -#include "AesGcmCipher.h" #include "TEBase.h" #include "backends/algebra.hpp" #include +#include "CipheredKey.h" +#include "Ciphertext.h" + namespace libBLS { constexpr size_t CYPHERTEXT_LENGTH = 64; - -constexpr size_t RANDOM_SECRET_SIZE_BYTES = MAX_FIELD_ELEMENT_SIZE_BYTES; using RandSecret = std::array< uint8_t, RANDOM_SECRET_SIZE_BYTES >; -/** - * @brief Holds the AES key ciphered via - * Thresgold Encryption - */ -struct CipheredKey { - static constexpr size_t CIPHERED_KEY_SIZE_BYTES = - algebra::G2Point::SIZE_BYTES + AES_256_KEY_SIZE_BYTES + algebra::G1Point::SIZE_BYTES; - - algebra::G2Point U; - AES256Key V; - algebra::G1Point W; - -public: - CipheredKey() = default; - CipheredKey( algebra::G2Point _U, AES256Key _V, algebra::G1Point _W, bool _validate = true ) - : U( _U ), V( std::move( _V ) ), W( _W ) { - if ( _validate ) - validate(); - } - - bool operator==( const CipheredKey& other ) const { - return ( U == other.U ) && ( V == other.V ) && ( W == other.W ); - } - - /** - * @brief Converts CipheredKey to bytes - * The byte format returned is: `[ U | V | W ]` - * where `U` and `W` are elements of G2 and G1 groups respectively, - * and `V` is a fixed size AES256Key. - * - * Final byte representation is always of fixed size. - */ - std::array< uint8_t, CIPHERED_KEY_SIZE_BYTES > toBytes() const { - std::array< uint8_t, CIPHERED_KEY_SIZE_BYTES > bytes; - uint8_t* source = bytes.data(); - // set U component - auto u_bytes = U.toByteArray(); - std::memcpy( source, u_bytes.data(), u_bytes.size() ); - source += u_bytes.size(); - // set V commponent - std::memcpy( source, V.data(), V.size() ); - source += V.size(); - // set W component - auto w_bytes = W.toByteArray(); - std::memcpy( source, w_bytes.data(), w_bytes.size() ); - - return bytes; - } - - /** - * @brief Converts bytes to CipheredKey - */ - static CipheredKey fromBytes( - std::array< uint8_t, CIPHERED_KEY_SIZE_BYTES > bytes, bool _validate = true ) { - std::array< uint8_t, G2_SIZE_BYTES > u_bytes; - std::array< uint8_t, AES_256_KEY_SIZE_BYTES > v_bytes; - std::array< uint8_t, G1_SIZE_BYTES > w_bytes; - - uint8_t* offset = bytes.data(); - // Get U bytes - std::memcpy( u_bytes.data(), offset, G2_SIZE_BYTES ); - offset += G2_SIZE_BYTES; - // Get V bytes - std::memcpy( v_bytes.data(), offset, AES_256_KEY_SIZE_BYTES ); - offset += AES_256_KEY_SIZE_BYTES; - // Get W bytes - std::memcpy( w_bytes.data(), offset, G1_SIZE_BYTES ); - - // Convert to CipheredKey components - algebra::G2Point U = algebra::G2Point::fromBytes( u_bytes ); - algebra::G1Point W = algebra::G1Point::fromBytes( w_bytes ); - - // constructor performs validation - return CipheredKey( U, v_bytes, W, _validate ); - } - - /** - * @brief Validates the CipheredKey - * @throw NotWellFormed if the key is not well formed - */ - void validate() const { - W.validate(); - U.validate(); - } - - static CipheredKey random() { - algebra::G2Point U = algebra::G2Point::random(); - AES256Key V; - RAND_bytes( V.data(), V.size() ); - algebra::G1Point W = algebra::G1Point::random(); - return CipheredKey( U, V, W ); - } - - /** - * Converts U component of the key to string - */ - std::string getDecryptionShareInput() { - U.toAffineCoordinates(); - return U.toString( Base::HEXA ); - } - - static std::vector< std::string > getDecryptionShareInputBatch( - const std::vector< CipheredKey >& keys ) { - std::vector< algebra::G2Point > U_points; - U_points.reserve( keys.size() ); - for ( const auto& key : keys ) { - U_points.push_back( key.U ); - } - - // batch convert all to affine coordinates - toAffineVec( U_points ); - - std::vector< std::string > res; - res.reserve( keys.size() ); - for ( const auto& U : U_points ) { - auto u_splitted = U.toString( Base::HEXA ); - res.push_back( u_splitted ); - } - - return res; - } - - const algebra::G2Point& getU() const { return U; } - const AES256Key& getV() const { return V; } - const algebra::G1Point& getW() const { return W; } -}; - -/** - * @brief Holds the ciphered AES keys (vector), as well as the data - * ciphered with AES key. - */ -struct Ciphertext { - std::vector< CipheredKey > keys; - std::shared_ptr< std::vector< uint8_t > > data; - -public: - bool operator==( const Ciphertext& other ) const { - bool baseParams = keys == other.keys; - if ( data && other.data ) { - return baseParams && ( *data == *other.data ); - } - return baseParams; - } - - Ciphertext() = default; - - Ciphertext( const std::vector< CipheredKey >& _keys, const std::vector< uint8_t >& _data, - bool _validate = true ) - : keys( _keys ), data( std::make_shared< std::vector< uint8_t > >( _data ) ) { - if ( _validate ) - validate(); - } - - Ciphertext( - const CipheredKey& _key, const std::vector< uint8_t >& _data, bool _validate = true ) - : keys( { _key } ), data( std::make_shared< std::vector< uint8_t > >( _data ) ) { - if ( _validate ) - validate(); - } - - // Constructor for exactly two keys - Ciphertext( const CipheredKey& _key1, const CipheredKey& _key2, - const std::vector< uint8_t >& _data, bool _validate = true ) - : keys( { _key1, _key2 } ), data( std::make_shared< std::vector< uint8_t > >( _data ) ) { - if ( _validate ) - validate(); - } - - const std::vector< uint8_t >& getData() const { - if ( !data ) { - throw ThresholdUtils::IncorrectInput( "Cyphertext data is not initialized" ); - } - return *data; - } - - /** - * @brief Converts Ciphertext to bytes - * The byte format returned is: `[ num_keys | key1 | key2 | ... | data ]` - * - * where `data` can be of arbitrary size, each `key` is a fixed size, - * and `num_keys` is a 1-byte integer indicating the number of keys. - */ - const std::vector< uint8_t > toBytes() const { - if ( !data ) { - throw ThresholdUtils::IncorrectInput( "Cyphertext data is not initialized" ); - } - - // Calculate total size needed - size_t totalSize = sizeof( uint8_t ) + // for number of keys - ( keys.size() * CipheredKey::CIPHERED_KEY_SIZE_BYTES ) + // for all keys - data->size(); // for data - - std::vector< uint8_t > bytes; - bytes.reserve( totalSize ); - - // Add number of keys - uint8_t numKeys = static_cast< uint8_t >( keys.size() ); - bytes.push_back( numKeys ); - - // Add each key - for ( const auto& key : keys ) { - std::array< uint8_t, CipheredKey::CIPHERED_KEY_SIZE_BYTES > keyBytes = key.toBytes(); - bytes.insert( bytes.end(), keyBytes.begin(), keyBytes.end() ); - } - - // Add data - bytes.insert( bytes.end(), data->begin(), data->end() ); - - return bytes; - } - - /** - * @brief Converts bytes to Ciphertext - */ - static Ciphertext fromBytes( std::vector< uint8_t >& bytes, bool _validate = true ) { - // we require at least 1 byte for num_keys + one key + random secret + 1 byte for data - if ( bytes.size() <= - sizeof( uint8_t ) + CipheredKey::CIPHERED_KEY_SIZE_BYTES + RANDOM_SECRET_SIZE_BYTES ) { - throw ThresholdUtils::IncorrectInput( "Cyphertext data is too short" ); - } - - size_t offset = 0; - - // Get number of keys - uint8_t numKeys; - std::memcpy( &numKeys, bytes.data() + offset, sizeof( uint8_t ) ); - offset += sizeof( uint8_t ); - - if ( numKeys == 0 || numKeys > 2 ) - throw ThresholdUtils::IncorrectInput( "Ciphertext must contain exactly 1 or 2 keys" ); - - // Check that the input matches the number of keys - size_t expectedMinSize = - sizeof( uint8_t ) + ( numKeys * CipheredKey::CIPHERED_KEY_SIZE_BYTES ) + - RANDOM_SECRET_SIZE_BYTES + 1; // +1 for at least 1 byte of actual data - if ( bytes.size() < expectedMinSize ) - throw ThresholdUtils::IncorrectInput( - "Cyphertext data is too short for specified number of keys" ); - - // Extract keys - std::vector< CipheredKey > keys; - keys.reserve( numKeys ); - - for ( size_t i = 0; i < numKeys; ++i ) { - std::array< uint8_t, CipheredKey::CIPHERED_KEY_SIZE_BYTES > keyBytes; - std::memcpy( - keyBytes.data(), bytes.data() + offset, CipheredKey::CIPHERED_KEY_SIZE_BYTES ); - offset += CipheredKey::CIPHERED_KEY_SIZE_BYTES; - - // do not validate CipheredKey here - // if validation is enabled, CipheredKey is validated in Ciphertext's constructor - // otherwise we don't need validation at all - keys.push_back( CipheredKey::fromBytes( keyBytes, false ) ); - } - - // Get data bytes - std::vector< uint8_t > data( bytes.begin() + offset, bytes.end() ); - - return Ciphertext( keys, data, _validate ); - } - - /** - * @brief Validates the Ciphertext - * @throw NotWellFormed if the it fails the validation - */ - void validate() const { - if ( keys.empty() || keys.size() > 2 ) - throw ThresholdUtils::IsNotWellFormed( "Ciphertext must contain exactly 1 or 2 keys" ); - - for ( const auto& key : keys ) { - key.validate(); - } - - if ( !data ) { - throw ThresholdUtils::IsNotWellFormed( "Cyphertext data is not initialized" ); - } - - // actual data without random secret must be at least 1 byte long - if ( data->size() <= RANDOM_SECRET_SIZE_BYTES ) { - throw ThresholdUtils::IsNotWellFormed( - "Cyphertext data is too short to hold random secret and at least 1 byte of data." ); - } - } - - /** - * @brief Returns the vector of keys in the Ciphertext - */ - const std::vector< CipheredKey >& getKeys() const { return keys; } - - /** - * @brief keeps only one key for validation and decryption - * @param idx - index of the key to keep - */ - void keepKey( size_t _idx ) { - if ( _idx >= keys.size() || keys.size() != 2 ) - throw ThresholdUtils::IncorrectInput( - "Key index is greater than number of the keys in ciphertext" ); - keys.erase( keys.begin() + ( keys.size() - _idx - 1 ) ); - } - - /** - * @brief get a CipheredKey for validation and decryption - * @throw IncorrectInput if there are 0 or 2 keys to choose - */ - const CipheredKey& getTargetKey() const { - if ( keys.size() != 1 ) - throw ThresholdUtils::IncorrectInput( "Cannot choose a target key" ); - return keys.front(); - } -}; - /** * @brief The result of the encryption process * Only accessible by the party that cyphers the text. - * `random_secret` should never be shared. + * `randomSecret` should never be shared. */ struct CipherResult { std::shared_ptr< Ciphertext > ciphertext; - RandSecret random_secret; + RandSecret randomSecret; }; struct CipheredKeyResult { std::vector< CipheredKey > ciphertext; - RandSecret random_secret; + RandSecret randomSecret; +}; + + +/** + * @brief Metadata used during encryption + */ +struct EncryptMetaData { + // Optional associated data for AES GCM + // Is used at encryption and at decryption stages only. Does not obey to threshold + // guarantees since it is associated with symmetric encryption. + std::optional< std::vector< uint8_t > > associatedDataAesGcm; + + // Optional associated data for TE + // Is used at encryption and validateEncryption stages only. Obeys to threshold guarantees + // as long as no party proceeds with the algorithm (partial decryption) in case + // validateEncryption fails with this associated data. + std::optional< std::vector< uint8_t > > associatedDataTE; + + // Optional seed used to derive both the random scalar secret and the AES key. + // Seed must have same size as AES_KEY in order to have the same entropy / security level. + std::optional< Seed256 > seed; }; class TE { @@ -377,7 +87,6 @@ class TE { ~TE(); - /** * @brief Encrypts a message using threshold encryption scheme * @@ -394,15 +103,22 @@ class TE { * * @note This is an auxiliar function, used within `encryptWithAES` */ - static CipheredKeyResult getCiphertext( - const AES256Key& key, const algebra::G2Point& commonPublic ); - static CipheredKeyResult getCiphertext( - const AES256Key& key, const std::vector< algebra::G2Point >& commonPublic ); + static CipheredKeyResult getCiphertext( const AES256Key& key, + const algebra::G2Point& commonPublic, + const std::optional< std::vector< uint8_t > >& associatedDataTE, + const std::optional< Seed256 >& seed ); + + static CipheredKeyResult getCiphertext( const AES256Key& key, + const std::vector< algebra::G2Point >& commonPublic, + const std::optional< std::vector< uint8_t > >& associatedDataTE, + const std::optional< Seed256 >& seed ); + + static CipherResult encryptWithAES( const std::vector< uint8_t >& message, + const algebra::G2Point& commonPublic, const EncryptMetaData& metaData = EncryptMetaData() ); - static CipherResult encryptWithAES( - const std::vector< uint8_t >& message, const algebra::G2Point& commonPublic ); static CipherResult encryptWithAES( const std::vector< uint8_t >& message, - const std::vector< algebra::G2Point >& commonPublic ); + const std::vector< algebra::G2Point >& commonPublic, + const EncryptMetaData& metaData = EncryptMetaData() ); static std::pair< std::string, RandSecret > encryptMessage( const std::vector< uint8_t >& message, const std::string& commonPublic ); @@ -410,18 +126,21 @@ class TE { const std::vector< uint8_t >& message, const std::vector< std::string >& commonPublic ); static algebra::G2Point getDecryptionShare( - const CipheredKey& ciphertext, const algebra::FrScalar& secret_key ); + const CipheredKey& ciphertext, const algebra::FrScalar& secretKey ); - static algebra::G1Point HashToGroup( const algebra::G2Point& U, const AES256Key& V ); + static algebra::G1Point HashToGroup( const algebra::G2Point& U, const AES256Key& V, + const std::vector< uint8_t >* associatedData = nullptr ); static std::string Hash( const algebra::G2Point& Y ); static bool Verify( const CipheredKey& ciphertext, const algebra::G2Point& decryptionShare, - const algebra::G2Point& public_key ); + const algebra::G2Point& publicKey, + const std::vector< uint8_t >* associatedDataTE = nullptr ); static std::vector< bool > VerifyBatch( const std::vector< CipheredKey >& ciphertexts, const std::vector< algebra::G2Point >& decryptionShares, - const std::vector< algebra::G2Point >& publicKeys ); + const std::vector< algebra::G2Point >& publicKeys, + const std::vector< std::vector< uint8_t > >* associatedDataTE = nullptr ); AES256Key CombineShares( const CipheredKey& ciphertext, const std::vector< std::pair< algebra::G2Point, size_t > >& decryptionShare ); diff --git a/tools/decryptMessage.cpp b/tools/decryptMessage.cpp index 0b97ef41..db5f5f2f 100644 --- a/tools/decryptMessage.cpp +++ b/tools/decryptMessage.cpp @@ -29,11 +29,13 @@ along with libBLS. If not, see . #include #include #include +#include int main( int argc, char* argv[] ) { - if ( argc != 5 ) { + if ( argc < 5 || argc > 7 ) { std::cerr << "Usage: " << argv[0] - << " " + << " " + "[ ]" << std::endl; return 1; } @@ -43,6 +45,17 @@ int main( int argc, char* argv[] ) { std::string expectedMessage = argv[3]; size_t keyIndexToKeep = std::stoul( argv[4] ); + std::optional< std::vector< uint8_t > > aadAes; + std::optional< std::vector< uint8_t > > aadTe; + + if ( argc >= 6 && argv[5] != nullptr && argv[5][0] != '\0' ) { + aadAes = libBLS::ThresholdUtils::hexCStringToBytes( argv[5] ); + } + + if ( argc >= 7 && argv[6] != nullptr && argv[6][0] != '\0' ) { + aadTe = libBLS::ThresholdUtils::hexCStringToBytes( argv[6] ); + } + libBLS::init(); auto encryptedDataBytes = libBLS::ThresholdUtils::hexCStringToBytes( encryptedData.c_str() ); @@ -56,7 +69,8 @@ int main( int argc, char* argv[] ) { auto encryptedMessage = ciphertext.getData(); - libBLS::ThresholdEncryption::validateEncryption( aesKeyEncrypted ); + libBLS::ThresholdEncryption::validateEncryption( + aesKeyEncrypted, aadTe.has_value() ? &aadTe.value() : nullptr ); libBLS::TEPrivateKeyShare privateKeyShare( libBLS::algebra::FrScalar::fromString( secretKey, libBLS::Base::DEC ), 1, 1, 1 ); @@ -65,8 +79,8 @@ int main( int argc, char* argv[] ) { libBLS::TEDecryptionShare decryptionShare = libBLS::ThresholdEncryption::partialDecrypt( aesKeyEncrypted, privateKeyShare ); - libBLS::ThresholdEncryption::validateDecryptionShare( - aesKeyEncrypted, decryptionShare, publicKeyShare ); + libBLS::ThresholdEncryption::validateDecryptionShare( aesKeyEncrypted, decryptionShare, + publicKeyShare, aadTe.has_value() ? &aadTe.value() : nullptr ); libBLS::TEDecryptSet decryptSet( 1, 1 ); decryptSet.addDecryptShare( decryptionShare ); @@ -77,10 +91,10 @@ int main( int argc, char* argv[] ) { libBLS::TEPublicKey publicKey( publicKeyShare.getPublicKeyRaw() ); libBLS::ThresholdEncryption::validateCombinedDecryption( - ciphertext, aesKeyDecrypted, publicKey ); + ciphertext, aesKeyDecrypted, publicKey, aadAes ); auto decryptedMessageBytes = - libBLS::ThresholdEncryption::decrypt( ciphertext, aesKeyDecrypted ); + libBLS::ThresholdEncryption::decrypt( ciphertext, aesKeyDecrypted, aadAes ); auto plaintext = libBLS::ThresholdUtils::bytesToHexString( decryptedMessageBytes ); diff --git a/tools/decrypt_message b/tools/decrypt_message index 47e5b8e6..598c6ae1 100755 Binary files a/tools/decrypt_message and b/tools/decrypt_message differ diff --git a/tools/utils.cpp b/tools/utils.cpp index 9f7908c6..76ba6934 100644 --- a/tools/utils.cpp +++ b/tools/utils.cpp @@ -191,8 +191,18 @@ std::string ThresholdUtils::bytesToHexString( const std::vector< uint8_t >& byte } std::vector< uint8_t > ThresholdUtils::hexCStringToBytes( const char* hexStr ) { + if ( hexStr == nullptr ) { + throw IncorrectInput( "Hex string is null." ); + } + size_t len = validateHexCString( hexStr ); + // Limit to 2MB hex string (1MB of decoded data) + constexpr size_t MAX_HEX_STRING_LENGTH = 1024 * 1024 * 2; + if ( len > MAX_HEX_STRING_LENGTH ) { + throw IncorrectInput( "Hex string exceeds maximum allowed length." ); + } + std::vector< uint8_t > bytes( len / 2 ); // Convert hex string to byte array