From 471d70069a684a23b6c11319643a73e005547a01 Mon Sep 17 00:00:00 2001 From: PETAce Date: Thu, 17 Aug 2023 15:43:01 -0700 Subject: [PATCH] feat: version 0.1.0 --- .clang-format | 55 +++ .gitattributes | 45 +++ .gitignore | 621 +++++++++++++++++++++++++++++++ .pre-commit-config.yaml | 17 + CHANGELOG.md | 10 + CMakeLists.txt | 393 +++++++++++++++++++ CODE_OF_CONDUCT.md | 127 +++++++ CONTRIBUTING.md | 61 +++ LICENSE | 201 ++++++++++ NOTICE | 1 + README.md | 76 ++++ bench/CMakeLists.txt | 83 +++++ bench/bench.cpp | 43 +++ bench/bench.h | 47 +++ bench/ec_openssl_bench.cpp | 100 +++++ bench/hash_bench.cpp | 56 +++ bench/prng_bench.cpp | 55 +++ bench/sampling_bench.cpp | 56 +++ cmake/EnableCXX17.cmake | 49 +++ cmake/EnableDebugFlags.cmake | 33 ++ cmake/ExternalBenchmark.cmake | 61 +++ cmake/ExternalGTest.cmake | 37 ++ cmake/ExternalOpenSSL.cmake | 53 +++ cmake/PETAce-SoloConfig.cmake.in | 81 ++++ cmake/SoloCustomMacros.cmake | 53 +++ example/CMakeLists.txt | 48 +++ example/example.cpp | 140 +++++++ licenses/LICENSE-BLAKE2.txt | 121 ++++++ src/solo/CMakeLists.txt | 37 ++ src/solo/ec_openssl.cpp | 431 +++++++++++++++++++++ src/solo/ec_openssl.h | 340 +++++++++++++++++ src/solo/hash.cpp | 195 ++++++++++ src/solo/hash.h | 277 ++++++++++++++ src/solo/prng.cpp | 220 +++++++++++ src/solo/prng.h | 233 ++++++++++++ src/solo/sampling.cpp | 68 ++++ src/solo/sampling.h | 80 ++++ src/solo/solo.h | 20 + src/solo/util/CMakeLists.txt | 43 +++ src/solo/util/aes_ecb_ctr.cpp | 197 ++++++++++ src/solo/util/aes_ecb_ctr.h | 45 +++ src/solo/util/blake2-impl.h | 136 +++++++ src/solo/util/blake2.h | 184 +++++++++ src/solo/util/blake2b-ref.c | 357 ++++++++++++++++++ src/solo/util/blake2xb-ref.c | 235 ++++++++++++ src/solo/util/config.h.in | 37 ++ src/solo/util/defines.h | 40 ++ test/CMakeLists.txt | 100 +++++ test/ec_openssl_test.cpp | 65 ++++ test/hash_test.cpp | 75 ++++ test/prng_test.cpp | 161 ++++++++ test/sampling_test.cpp | 50 +++ test/test_runner.cpp | 23 ++ tools/clang-format-all.sh | 27 ++ 54 files changed, 6399 insertions(+) create mode 100644 .clang-format create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 .pre-commit-config.yaml create mode 100644 CHANGELOG.md create mode 100644 CMakeLists.txt create mode 100644 CODE_OF_CONDUCT.md create mode 100644 CONTRIBUTING.md create mode 100644 LICENSE create mode 100644 NOTICE create mode 100644 README.md create mode 100644 bench/CMakeLists.txt create mode 100644 bench/bench.cpp create mode 100644 bench/bench.h create mode 100644 bench/ec_openssl_bench.cpp create mode 100644 bench/hash_bench.cpp create mode 100644 bench/prng_bench.cpp create mode 100644 bench/sampling_bench.cpp create mode 100644 cmake/EnableCXX17.cmake create mode 100644 cmake/EnableDebugFlags.cmake create mode 100644 cmake/ExternalBenchmark.cmake create mode 100644 cmake/ExternalGTest.cmake create mode 100644 cmake/ExternalOpenSSL.cmake create mode 100644 cmake/PETAce-SoloConfig.cmake.in create mode 100644 cmake/SoloCustomMacros.cmake create mode 100644 example/CMakeLists.txt create mode 100644 example/example.cpp create mode 100644 licenses/LICENSE-BLAKE2.txt create mode 100644 src/solo/CMakeLists.txt create mode 100644 src/solo/ec_openssl.cpp create mode 100644 src/solo/ec_openssl.h create mode 100644 src/solo/hash.cpp create mode 100644 src/solo/hash.h create mode 100644 src/solo/prng.cpp create mode 100644 src/solo/prng.h create mode 100644 src/solo/sampling.cpp create mode 100644 src/solo/sampling.h create mode 100644 src/solo/solo.h create mode 100644 src/solo/util/CMakeLists.txt create mode 100644 src/solo/util/aes_ecb_ctr.cpp create mode 100644 src/solo/util/aes_ecb_ctr.h create mode 100644 src/solo/util/blake2-impl.h create mode 100644 src/solo/util/blake2.h create mode 100644 src/solo/util/blake2b-ref.c create mode 100644 src/solo/util/blake2xb-ref.c create mode 100644 src/solo/util/config.h.in create mode 100644 src/solo/util/defines.h create mode 100644 test/CMakeLists.txt create mode 100644 test/ec_openssl_test.cpp create mode 100644 test/hash_test.cpp create mode 100644 test/prng_test.cpp create mode 100644 test/sampling_test.cpp create mode 100644 test/test_runner.cpp create mode 100644 tools/clang-format-all.sh diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..ec20a8a --- /dev/null +++ b/.clang-format @@ -0,0 +1,55 @@ +--- +IndentWidth: 4 +TabWidth: 4 + +Language: Cpp +Standard: Cpp11 +BasedOnStyle: Google +# indent +AccessModifierOffset: -4 +ContinuationIndentWidth: 8 +# align +BreakBeforeTernaryOperators: true +BreakBeforeBinaryOperators: false +AlignAfterOpenBracket: false +ColumnLimit: 120 +# constructor +BreakConstructorInitializersBeforeComma: false +ConstructorInitializerIndentWidth: 8 +ConstructorInitializerAllOnOneLineOrOnePerLine: true +# short block +AllowShortBlocksOnASingleLine: false +AllowShortFunctionsOnASingleLine: false +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +Cpp11BracedListStyle: true +# other +AlwaysBreakTemplateDeclarations: true +DerivePointerAlignment: false +PointerAlignment: Left + +# clang-format 3.9+ +SortIncludes: true +BreakStringLiterals: false +ReflowComments: true + +# custom +IncludeCategories: +# Matches common headers first, but sorts them after project includes +- Regex: '^<.*.h>' + Priority: 1 +- Regex: '^<.*.hpp>' + Priority: 2 +- Regex: '^<[^/]*>' + Priority: 2 +- Regex: '^"duet/.*' + Priority: 5 +- Regex: '^"solo/.*' + Priority: 4 +- Regex: '^"verse/.*' + Priority: 4 +- Regex: '^"network/.*' + Priority: 4 +- Regex: '^".*' + Priority: 3 +... diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..957b5c2 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,45 @@ +############################### +# Git Line Endings # +############################### +# Set default behaviour to automatically normalize line endings. +* text=auto + +############################### +# C++ Attributes # +############################### +# Sources +*.c text diff=cpp +*.cc text diff=cpp +*.cxx text diff=cpp +*.cpp text diff=cpp +*.c++ text diff=cpp +*.hpp text diff=cpp +*.h text diff=cpp +*.h++ text diff=cpp +*.hh text diff=cpp + +# Compiled Object files +*.slo binary +*.lo binary +*.o binary +*.obj binary + +# Precompiled Headers +*.gch binary +*.pch binary + +# Compiled Dynamic libraries +*.so binary +*.dylib binary +*.dll binary + +# Compiled Static libraries +*.lai binary +*.la binary +*.a binary +*.lib binary + +# Executables +*.exe binary +*.out binary +*.app binary diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..21e5d71 --- /dev/null +++ b/.gitignore @@ -0,0 +1,621 @@ +# Configuration +!cmake/*.cmake.in +!cmake/*.cmake +cmake/*Config.cmake +cmake/*ConfigVersion.cmake +cmake/*Targets.cmake +.vscode +CMakeSettings.json + +# Build +bin/ +lib/ +build/ +*.build +env +thirdparty/ + +######################################################################################## +#### The following are based on templates from https://github.com/github/gitignore. #### +######################################################################################## + +*.pc +.config +*.args.json +# You Complete Me +.ycm_extra_conf.py +# Vim +.vimrc +.lvimrc +.local_vimrc + +#### Archives #### +# It's better to unpack these files and commit the raw source because +# git has its own built in compression methods. +*.7z +*.jar +*.rar +*.zip +*.gz +*.gzip +*.tgz +*.bzip +*.bzip2 +*.bz2 +*.xz +*.lzma +*.cab +*.xar +# Packing-only formats +*.iso +*.tar +# Package management formats +*.dmg +*.xpi +*.gem +*.egg +*.deb +*.rpm +*.msi +*.msm +*.msp +*.txz + +#### Backup #### +*.bak +*.gho +*.ori +*.orig +*.tmp + +#### Git #### +*.patch +*.diff + +#### Windows #### +# Windows thumbnail cache files +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db +# Dump file +*.stackdump +# Folder config file +[Dd]esktop.ini +# Recycle Bin used on file shares +$RECYCLE.BIN/ +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp +# Windows shortcuts +*.lnk + +#### Linux #### +*~ +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* +# KDE directory preferences +.directory +# Linux trash folder which might appear on any partition or disk +.Trash-* +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +#### macOS #### +# General +.DS_Store +.AppleDouble +.LSOverride +# Icon must end with two \r +Icon +# Thumbnails +._* +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +#### Autotools #### +# http://www.gnu.org/software/automake +Makefile.in +/ar-lib +/mdate-sh +/py-compile +/test-driver +/ylwrap +.deps/ +.dirstamp +# http://www.gnu.org/software/autoconf +autom4te.cache +/autoscan.log +/autoscan-*.log +/aclocal.m4 +/compile +/config.guess +/config.h.in +/config.log +/config.status +/config.sub +/configure +/configure.scan +/depcomp +/install-sh +/missing +/stamp-h1 +# https://www.gnu.org/software/libtool/ +/ltmain.sh +# http://www.gnu.org/software/texinfo +/texinfo.tex +# http://www.gnu.org/software/m4/ +m4/libtool.m4 +m4/ltoptions.m4 +m4/ltsugar.m4 +m4/ltversion.m4 +m4/lt~obsolete.m4 +# Generated Makefile +# (meta build system like autotools, +# can automatically generate from config.status script +# (which is called by configure script)) +Makefile + +#### C/C++ #### +# Prerequisites +*.d +# Compiled Object files +*.slo +*.lo +*.ko +*.o +*.obj +*.elf +# Linker output +*.lik +*.map +*.exp +# Precompiled Headers +*.gch +*.pch +# Compiled Static libraries +*.lai +*.la +*.a +*.lib +# Compiled Dynamic libraries +*.so +*.so.* +*.dylib +*.dll +# Kernel Module Compile Results +*.mod* +*.smod +*.cmd +.tmp_versions/ +modules.order +Module.symvers +Mkfile.old +dkms.conf +# Executables +*.exe +*.out +*.app +*.i*86 +*.x86_64 +*.hex +# Debug files +*.dSYM/ +*.su +*.idb +*.pdb + +#### CMake #### +CMakeLists.txt.user +CMakeCache.txt +CMakeFiles +CMakeScripts +Testing +Makefile +cmake_install.cmake +install_manifest.txt +compile_commands.json +CTestTestfile.cmake +CPackConfig.cmake +CPackSourceConfig.cmake + +#### Gradle #### +.gradle +**/build/ +!src/**/build/ +# Ignore Gradle GUI config +gradle-app.setting +# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) +!gradle-wrapper.jar +# Cache of project +.gradletasknamecache +# # Work around https://youtrack.jetbrains.com/issue/IDEA-116898 +# gradle/wrapper/gradle-wrapper.properties +android/.idea/ + +#### Visual Studio Code #### +.vscode/* +#!.vscode/settings.json +#!.vscode/tasks.json +#!.vscode/launch.json +#!.vscode/extensions.json +*.code-workspace +# Local History for Visual Studio Code +.history/ + +#### Visual Stduio #### +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*[.json, .xml, .info] + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..1a5615d --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,17 @@ +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.4.0 + hooks: + - id: check-added-large-files + - id: check-case-conflict + - id: check-merge-conflict + - id: check-symlinks + - id: check-yaml + - id: end-of-file-fixer + - id: forbid-submodules + - id: mixed-line-ending + - id: trailing-whitespace + - repo: https://github.com/pre-commit/mirrors-clang-format + rev: v16.0.0 + hooks: + - id: clang-format diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..32fb996 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,10 @@ +# List of Changes + +## Version 0.1.0 + +### Features + +- Added hash function: SHA-256, SHA3-256, and BLAKE2b. +- Added psuedo-random number generators based on: SHAKE_128, BLAKE2Xb, and AES_ECB_CTR. +- Added sampling of bytes, 32-bit unsigned integers, and 64-bit unsigned integers from the uniform distribution. +- Added prime field elliptic curve group arithmetics including hash-to-curve. diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..1c29587 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,393 @@ +# Copyright 2023 TikTok Pte. Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +cmake_minimum_required(VERSION 3.14) + +################################################### +# Project SOLO includes the following components: # +# 1. SOLO C++ library # +# 2. SOLO C++ test # +# 3. SOLO C++ bench # +# 4. SOLO C++ example # +################################################### + +# [OPTION] CMAKE_BUILD_TYPE (DEFAULT: "Release") +# Select from Release, Debug, MiniSizeRel, or RelWithDebInfo. +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Build type" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY + STRINGS "Release" "Debug" "MinSizeRel" "RelWithDebInfo") +endif() +message(STATUS "Build type (CMAKE_BUILD_TYPE): ${CMAKE_BUILD_TYPE}") + +project(SOLO VERSION 0.1.0 LANGUAGES CXX C) + +######################## +# Global configuration # +######################## + +# CMake modules +include(CMakeDependentOption) +include(CMakePushCheckState) +include(CheckIncludeFileCXX) +include(CheckCXXSourceCompiles) +include(CheckCXXSourceRuns) + +# Custom modules +list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/cmake) +include(SoloCustomMacros) + +# In Debug mode, define SOLO_DEBUG. +if(CMAKE_BUILD_TYPE STREQUAL "Debug") + set(SOLO_DEBUG ON) +else() + set(SOLO_DEBUG OFF) +endif() +message(STATUS "SOLO debug mode: ${SOLO_DEBUG}") + +# In Debug mode, enable extra compiler flags. +include(EnableDebugFlags) + +# Always build position-independent-code +set(CMAKE_POSITION_INDEPENDENT_CODE ON) + +# [OPTION] SOLO_USE_CXX17 (default: OFF) +# Use C++17, use C++14 otherwise. +set(SOLO_USE_CXX17_OPTION_STR "Use C++17") +option(SOLO_USE_CXX17 ${SOLO_USE_CXX17_OPTION_STR} OFF) +message(STATUS "SOLO_USE_CXX17: ${SOLO_USE_CXX17}") +# Enable features from C++17 if available, disable features if set to OFF. +include(EnableCXX17) + +# Add default files and directories. +include(GNUInstallDirs) + +# Runtime path +set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}") +set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE) + +# Source Tree +set(SOLO_INCLUDES_DIR ${CMAKE_CURRENT_LIST_DIR}/src) +set(SOLO_CONFIG_IN_FILENAME ${CMAKE_CURRENT_LIST_DIR}/cmake/PETAce-SoloConfig.cmake.in) +set(SOLO_CONFIG_H_IN_FILENAME ${SOLO_INCLUDES_DIR}/solo/util/config.h.in) + +# Build tree +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/lib) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/lib) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/bin) +set(SOLO_CONFIG_FILENAME ${CMAKE_CURRENT_BINARY_DIR}/cmake/PETAce-SoloConfig.cmake) +set(SOLO_TARGETS_FILENAME ${CMAKE_CURRENT_BINARY_DIR}/cmake/PETAce-SoloTargets.cmake) +set(SOLO_CONFIG_VERSION_FILENAME ${CMAKE_CURRENT_BINARY_DIR}/cmake/PETAce-SoloConfigVersion.cmake) +set(SOLO_CONFIG_H_FILENAME ${CMAKE_CURRENT_BINARY_DIR}/src/solo/util/config.h) +set(SOLO_THIRDPARTY_DIR ${CMAKE_CURRENT_BINARY_DIR}/thirdparty) + +# Installation tree +set(SOLO_CONFIG_INSTALL_DIR ${CMAKE_INSTALL_LIBDIR}/cmake/PETAce-Solo-${SOLO_VERSION_MAJOR}.${SOLO_VERSION_MINOR}) +set(SOLO_INCLUDES_INSTALL_DIR ${CMAKE_INSTALL_INCLUDEDIR}/PETAce-${SOLO_VERSION_MAJOR}.${SOLO_VERSION_MINOR}) +set(SOLO_THIRDPARTY_INCLUDES_INSTALL_DIR ${SOLO_INCLUDES_INSTALL_DIR}/thirdparty) + +# Make the install target depend on the all target. +set(CMAKE_SKIP_INSTALL_ALL_DEPENDENCY OFF) + +# Supported target operating systems are Linux and macOS. +if (NOT DEFINED LINUX) + if (UNIX AND NOT APPLE AND NOT CYGWIN AND NOT MINGW) + set(LINUX ON) + endif() +endif() +if (UNIX AND APPLE) + set(MACOS ON) +endif() +if (NOT LINUX AND NOT MACOS) + message(FATAL_ERROR "Supported target operating systems are Linux and macOS") +endif() + +# Only support x86_64 and arm64 +set(CMAKE_REQUIRED_QUIET_OLD ${CMAKE_REQUIRED_QUIET}) +set(CMAKE_REQUIRED_QUIET ON) +CHECK_CXX_SOURCE_RUNS(" + #if defined(__aarch64__) + int main() { + return 0; + } + #else + #error + #endif + " + SOLO_ARM64 +) +CHECK_CXX_SOURCE_RUNS(" + #if defined(__amd64) + int main() { + return 0; + } + #else + #error + #endif + " + SOLO_AMD64 +) +set(CMAKE_REQUIRED_QUIET ${CMAKE_REQUIRED_QUIET_OLD}) +if (NOT SOLO_AMD64 AND NOT SOLO_ARM64) + message(FATAL_ERROR "Supported target architectures are x86_64 and arm64") +endif() + +# AES, SSE, and AVX +CHECK_INCLUDE_FILE_CXX("wmmintrin.h" SOLO_USE_AES_INTRIN) +if (SOLO_USE_AES_INTRIN) + add_compile_options(-msse4.2 -mavx -maes) +endif() + +# Enable test coverage +set(SOLO_ENABLE_GCOV_STR "Enable gcov") +option(SOLO_ENABLE_GCOV ${SOLO_ENABLE_GCOV_STR} OFF) +message(STATUS "SOLO_ENABLE_GCOV: ${SOLO_ENABLE_GCOV_STR}") +if(CMAKE_BUILD_TYPE STREQUAL "Debug" AND SOLO_ENABLE_GCOV) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fprofile-arcs -ftest-coverage") + set(CMAKE_CXX_LINK_EXECUTABLE "${CMAKE_CXX_LINK_EXECUTABLE} -fprofile-arcs -ftest-coverage -lgcov") +endif() + +set(CMAKE_CXX_LINK_EXECUTABLE "${CMAKE_CXX_LINK_EXECUTABLE} -ldl -lrt") + +######################### +# External dependencies # +######################### + +# [OPTION] SOLO_BUILD_DEPS (DEFAULT: ON) +# Download and build dependencies if set to ON. +# Look for dependencies using find_package, otherwise. +set(SOLO_BUILD_DEPS_OPTION_STR "Automatically download and build unmet dependencies") +option(SOLO_BUILD_DEPS ${SOLO_BUILD_DEPS_OPTION_STR} ON) +message(STATUS "SOLO_BUILD_DEPS: ${SOLO_BUILD_DEPS}") + +if(SOLO_BUILD_DEPS) + include(FetchContent) + mark_as_advanced(FETCHCONTENT_BASE_DIR) + mark_as_advanced(FETCHCONTENT_FULLY_DISCONNECTED) + mark_as_advanced(FETCHCONTENT_UPDATES_DISCONNECTED) + mark_as_advanced(FETCHCONTENT_QUIET) +endif() + +# OpenSSL +find_package(OpenSSL 1.1.1 QUIET) +if(OpenSSL_FOUND) + message(STATUS "OpenSSL: found") + set(openssl "OpenSSL::Crypto") +else() + if(SOLO_BUILD_DEPS) + message(STATUS "OpenSSL: download ...") + solo_fetch_thirdparty_content(ExternalOpenSSL) + set(openssl "crypto") + set(SOLO_BUILD_OPENSSL TRUE) + else() + message(FATAL_ERROR "OpenSSL: not found, please download and install manually") + endif() +endif() + +# Require Threads::Threads +if(NOT TARGET Threads::Threads) + set(CMAKE_THREAD_PREFER_PTHREAD TRUE) + set(THREADS_PREFER_PTHREAD_FLAG TRUE) + find_package(Threads REQUIRED) +endif() + +#################### +# SOLO C++ library # +#################### + +# [OPTION] SOLO_BUILD_SHARED_LIBS (DEFAULT: OFF) +# Build a shared library if set to ON. +set(SOLO_BUILD_SHARED_LIBS_STR "Build shared library") +option(SOLO_BUILD_SHARED_LIBS ${SOLO_BUILD_SHARED_LIBS_STR} OFF) +message(STATUS "SOLO_BUILD_SHARED_LIBS: ${SOLO_BUILD_SHARED_LIBS}") + +# Add source files to library and header files to install +set(SOLO_SOURCE_FILES "") +add_subdirectory(src/solo) + +# Create the config file +configure_file(${SOLO_CONFIG_H_IN_FILENAME} ${SOLO_CONFIG_H_FILENAME}) +install( + FILES ${SOLO_CONFIG_H_FILENAME} + DESTINATION ${SOLO_INCLUDES_INSTALL_DIR}/solo/util) + +# Build only a static library +if(NOT SOLO_BUILD_SHARED_LIBS) + add_library(solo STATIC ${SOLO_SOURCE_FILES}) + if(SOLO_USE_CXX17) + target_compile_features(solo PUBLIC cxx_std_17) + else() + target_compile_features(solo PUBLIC cxx_std_14) + endif() + target_include_directories(solo PUBLIC + $ + $) + target_include_directories(solo PUBLIC + $) + set_target_properties(solo PROPERTIES VERSION ${SOLO_VERSION}) + set_target_properties(solo PROPERTIES OUTPUT_NAME petace_solo-${SOLO_VERSION_MAJOR}.${SOLO_VERSION_MINOR}) + install(TARGETS solo EXPORT PETAce-SoloTargets + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) + + if(SOLO_BUILD_OPENSSL) + add_dependencies(solo ${openssl}) + target_include_directories(solo PUBLIC $) + target_include_directories(solo PUBLIC $>) + target_include_directories(solo PUBLIC $) + solo_combine_archives(solo ${openssl}) + set(SOLO_CARRY_OPENSSL TRUE) + else() + target_link_libraries(solo PUBLIC ${openssl}) + set(SOLO_CARRY_OPENSSL FALSE) + endif() + + target_link_libraries(solo PUBLIC Threads::Threads) + +# Build only a shared library +else() + add_library(solo_shared SHARED ${SOLO_SOURCE_FILES}) + if(SOLO_USE_CXX17) + target_compile_features(solo_shared PUBLIC cxx_std_17) + else() + target_compile_features(solo_shared PUBLIC cxx_std_14) + endif() + target_include_directories(solo_shared PUBLIC + $ + $) + target_include_directories(solo_shared PUBLIC + $) + set_target_properties(solo_shared PROPERTIES VERSION ${SOLO_VERSION}) + set_target_properties(solo_shared PROPERTIES OUTPUT_NAME petace_solo) + set_target_properties(solo_shared PROPERTIES SOVERSION ${SOLO_VERSION_MAJOR}.${SOLO_VERSION_MINOR}) + install(TARGETS solo_shared EXPORT PETAce-SoloTargets + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) + + if(SOLO_BUILD_OPENSSL) + target_link_libraries(solo_shared PUBLIC ${openssl}) + target_include_directories(solo_shared PUBLIC $) + target_include_directories(solo_shared PUBLIC $>) + target_include_directories(solo_shared PUBLIC $) + else() + target_link_libraries(solo_shared PUBLIC ${openssl}) + endif() + set(SOLO_CARRY_OPENSSL FALSE) + + target_link_libraries(solo_shared PUBLIC Threads::Threads) +endif() + +# Add standard alias targets for PETAce-Solo::solo and PETAce-Solo::solo_shared +if(TARGET solo) + add_library(PETAce-Solo::solo ALIAS solo) +endif() +if(TARGET solo_shared) + add_library(PETAce-Solo::solo_shared ALIAS solo_shared) +endif() + +################################# +# Installation and CMake config # +################################# + +# Create the CMake config file +include(CMakePackageConfigHelpers) +configure_package_config_file( + ${SOLO_CONFIG_IN_FILENAME} ${SOLO_CONFIG_FILENAME} + INSTALL_DESTINATION ${SOLO_CONFIG_INSTALL_DIR}) + +# Install the export +install( + EXPORT PETAce-SoloTargets + NAMESPACE PETAce-Solo:: + DESTINATION ${SOLO_CONFIG_INSTALL_DIR}) + +# Version file; we require exact version match for downstream +write_basic_package_version_file( + ${SOLO_CONFIG_VERSION_FILENAME} + VERSION ${SOLO_VERSION} + COMPATIBILITY SameMinorVersion) + +# Install config and module files +install( + FILES + ${SOLO_CONFIG_FILENAME} + ${SOLO_CONFIG_VERSION_FILENAME} + DESTINATION ${SOLO_CONFIG_INSTALL_DIR}) + +# We export PETAce-SoloTargets from the build tree so it can be used by other projects +# without requiring an install. +export( + EXPORT PETAce-SoloTargets + NAMESPACE PETAce-Solo:: + FILE ${SOLO_TARGETS_FILENAME}) + +# Install header files of dependencies if SOLO_BUILD_DEPS is ON +if(SOLO_BUILD_DEPS) + # Insert dependencies here + if(SOLO_BUILD_OPENSSL) + install( + FILES ${OPENSSL_CRYPTO_LIBRARY} ${OPENSSL_SSL_LIBRARY} + DESTINATION ${CMAKE_INSTALL_LIBDIR}) + install( + DIRECTORY ${OPENSSL_INCLUDE_DIR} + DESTINATION ${SOLO_THIRDPARTY_INCLUDES_INSTALL_DIR}/openssl) + endif() +endif() + +#################### +# SOLO C++ example # +#################### + +# [option] SOLO_BUILD_EXAMPLE +set(SOLO_BUILD_EXAMPLE_OPTION_STR "Build C++ example for SOLO") +option(SOLO_BUILD_EXAMPLE ${SOLO_BUILD_EXAMPLE_OPTION_STR} ON) +message(STATUS "SOLO_BUILD_EXAMPLE: ${SOLO_BUILD_EXAMPLE}") + +if(SOLO_BUILD_EXAMPLE) + add_subdirectory(example) +endif() + +################# +# SOLO C++ test # +################# + +# [option] SOLO_BUILD_TEST +set(SOLO_BUILD_TEST_OPTION_STR "Build C++ test for SOLO") +option(SOLO_BUILD_TEST ${SOLO_BUILD_TEST_OPTION_STR} ON) +message(STATUS "SOLO_BUILD_TEST: ${SOLO_BUILD_TEST}") + +if(SOLO_BUILD_TEST) + add_subdirectory(test) + + if(CMAKE_BUILD_TYPE STREQUAL "Debug" AND SOLO_ENABLE_GCOV) + add_custom_target(test_coverage + COMMAND gcovr -r ${CMAKE_CURRENT_LIST_DIR} -f \"src\" -e \".+\(test\\.cpp\)\" --xml-pretty -o "${CMAKE_CURRENT_BINARY_DIR}/report/coverage.xml" + WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}) + endif() +endif() + +################## +# SOLO C++ bench # +################## + +# [option] SOLO_BUILD_BENCH +set(SOLO_BUILD_BENCH_OPTION_STR "Build C++ benchmark for SOLO") +option(SOLO_BUILD_BENCH ${SOLO_BUILD_BENCH_OPTION_STR} ON) +message(STATUS "SOLO_BUILD_BENCH: ${SOLO_BUILD_BENCH}") + +if(SOLO_BUILD_BENCH) + add_subdirectory(bench) +endif() diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..41dd9eb --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,127 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, religion, or sexual identity +and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the + overall community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or + advances of any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email + address, without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series +of actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or +permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within +the community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.0, available at +https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. + +Community Impact Guidelines were inspired by [Mozilla's code of conduct +enforcement ladder](https://github.com/mozilla/diversity). + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see the FAQ at +https://www.contributor-covenant.org/faq. Translations are available at +https://www.contributor-covenant.org/translations. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..c96bdf8 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,61 @@ +# Contributing to PETAce-Solo + +Thank you for investing your time in contributing to PETAce-Solo! + +Read our [Code of Coduct](./CODE_OF_CONDUCT.md) to keep our community approachable and respectable. + +This guide details how to use issues and pull requests to improve PETAce-Solo. + +## General Guidelines + +### Pull Requests + +Make sure to keep Pull Requests small and functional to make them easier to review, understand, and look up in commit history. This repository uses "Squash and Commit" to keep our history clean and make it easier to revert changes based on PR. + +Adding the appropriate documentation, unit tests and e2e tests as part of a feature is the responsibility of the feature owner, whether it is done in the same Pull Request or not. + +Pull Requests should follow the "subject: message" format, where the subject describes what part of the code is being modified. + +Refer to the template for more information on what goes into a PR description. + +### Design Docs + +A contributor proposes a design with a PR on the repository to allow for revisions and discussions. If a design needs to be discussed before formulating a document for it, make use of Google doc and GitHub issue to involve the community on the discussion. + +### GitHub Issues + +GitHub Issues are used to file bugs, work items, and feature requests with actionable items/issues (Please refer to the "Reporting Bugs/Feature Requests" section below for more information). + +### Reporting Bugs/Feature Requests + +We welcome you to use the GitHub issue tracker to report bugs or suggest features that have actionable items/issues (as opposed to introducing a feature request on GitHub Discussions). + +When filing an issue, please check existing open, or recently closed, issues to make sure somebody else hasn't already reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: + +- A reproducible test case or series of steps +- The version of the code being used +- Any modifications you've made relevant to the bug +- Anything unusual about your environment or deployment + +## Contributing via Pull Requests + +### Find interesting issue + +If you spot a problem with the problem, [search if an issue already exists](https://github.com/tiktok-privacy-innnovation/PETAce-Solo/issues). If a related issue doesn't exist, you can open a new issue using [issue template](https://github.com/tiktok-privacy-innnovation/PETAce-Solo/issues/new/choose). + +### Solve an issue + +Please check `DEVELOPMENT.md` in sub folder to get familar with running and testing codes. + +### Open a Pull request. + +When you're done making the changes, open a pull request and fill PR template so we can better review your PR. The template helps reviewers understand your changes and the purpose of your pull request. + +Don't forget to link PR to issue if you are solving one. + +If you run into any merge issues, checkout this [git tutorial](https://lab.github.com/githubtraining/managing-merge-conflicts) to help you resolve merge conflicts and other issues. + + +## Finding contributions to work on + +Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any 'help wanted' and 'good first issue' issues are a great place to start. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/NOTICE b/NOTICE new file mode 100644 index 0000000..f3bcf5a --- /dev/null +++ b/NOTICE @@ -0,0 +1 @@ +Copyright 2023 TikTok Pte. Ltd. diff --git a/README.md b/README.md new file mode 100644 index 0000000..410963e --- /dev/null +++ b/README.md @@ -0,0 +1,76 @@ +# PETAce-Solo + +PETAce-Solo is a C++ library that implements or wraps primitive cryptography schemes. +It is one of the many components in [the framework PETAce](https://github.com/tiktok-privacy-innovation/PETAce). + +PETAce-Solo implements or wraps the following primitives that involves only one party, as implied by the same "Solo". +- Hash function: SHA-256, SHA3-256, and BLAKE2b. +- Psuedo-random number generators based on: SHAKE_128, BLAKE2Xb, and AES_ECB_CTR. +- Sampling of bytes, 32-bit unsigned integers, and 64-bit unsigned integers from the uniform distribution. +- Prime field elliptic curve group arithmetics including hash-to-curve. + +## Requirements + +| System | Toolchain | +|--------|-------------------------------------------------------| +| Linux | Clang++ (>= 5.0) or GNU G++ (>= 5.5), CMake (>= 3.15) | + +| Required dependency | Tested version | Use | +|-----------------------------------------------|----------------|--------------------------| +| [OpenSSL](https://github.com/openssl/openssl) | 1.1.1 | Cryptographic primitives | + +| Optional dependency | Tested version | Use | +|--------------------------------------------------------|----------------|------------------------| +| [GoogleTest](https://github.com/google/googletest) | 1.12.1 | For running tests | +| [GoogleBenchmark](https://github.com/google/benchmark) | 1.6.1 | For running benchmarks | + +## Building PETAce-Solo + +We assume that all commands presented below are executed in the root directory of PETAce-Solo. + +To build PETAce-Solo library (optionally with test, benchmark, and example): + +```bash +cmake -S . -B build -DSOLO_BUILD_TEST=ON -DSOLO_BUILD_BENCH=ON -DSOLO_BUILD_EXAMPLE=ON +cmake --build build +``` + +Output binaries can be found in `build/lib/` and `build/bin/` directories. + +| Compile Options | Values | Default | Description | +|--------------------------|---------------|---------|-----------------------------------------------------| +| `CMAKE_BUILD_TYPE` | Release/Debug | Release | Debug mode decreases run-time performance. | +| `SOLO_BUILD_SHARED_LIBS` | ON/OFF | OFF | Build a shared library if set to ON. | +| `SOLO_BUILD_BENCH` | ON/OFF | ON | Build C++ benchmark if set to ON. | +| `SOLO_BUILD_EXAMPLE` | ON/OFF | ON | Build C++ example if set to ON. | +| `SOLO_BUILD_TEST` | ON/OFF | ON | Build C++ test if set to ON. | +| `SOLO_BUILD_DEPS` | ON/OFF | ON | Download and build unmet dependencies if set to ON. | + +## Contribution + +Please check [Contributing](CONTRIBUTING.md) for more details. + +## Code of Conduct + +Please check [Code of Conduct](CODE_OF_CONDUCT.md) for more details. + +## License + +This project is licensed under the [Apache-2.0 License](LICENSE). + +## Citing PETAce + +To cite PETAce in academic papers, please use the following BibTeX entries. + +### Version 0.1.0 + +```tex + @misc{petace, + title = {PETAce (release 0.1.0)}, + howpublished = {\url{https://github.com/tiktok-privacy-innovation/PETAce}}, + month = July, + year = 2023, + note = {TikTok Pte. Ltd.}, + key = {PETAce} + } +``` diff --git a/bench/CMakeLists.txt b/bench/CMakeLists.txt new file mode 100644 index 0000000..00f76cb --- /dev/null +++ b/bench/CMakeLists.txt @@ -0,0 +1,83 @@ +# Copyright 2023 TikTok Pte. Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +cmake_minimum_required(VERSION 3.14) + +project(SOLOBench VERSION 0.1.0 LANGUAGES CXX) + +# If not called from root CMakeLists.txt +if(NOT DEFINED SOLO_BUILD_BENCH) + set(SOLO_BUILD_BENCH ON) + + find_package(PETAce-Solo 0.1.0 EXACT REQUIRED) + + # Must define these variables and include macros + set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/lib) + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/bin) + set(SOLO_THIRDPARTY_DIR ${CMAKE_CURRENT_BINARY_DIR}/thirdparty) + set(THIRDPARTY_BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/thirdparty) + include(FetchContent) + mark_as_advanced(FETCHCONTENT_BASE_DIR) + mark_as_advanced(FETCHCONTENT_FULLY_DISCONNECTED) + mark_as_advanced(FETCHCONTENT_UPDATES_DISCONNECTED) + mark_as_advanced(FETCHCONTENT_QUIET) + list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/../cmake) + include(SoloCustomMacros) +else() + set(THIRDPARTY_BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/../thirdparty) +endif() + +if(NOT DEFINED SOLO_BUILD_DEPS) + # [option] SOLO_BUILD_DEPS (default: ON) + # Download and build missing dependencies, throw error if disabled. + set(SOLO_BUILD_DEPS_OPTION_STR "Automatically download and build unmet dependencies") + option(SOLO_BUILD_DEPS ${SOLO_BUILD_DEPS_OPTION_STR} ON) +endif() + +# if SOLO_BUILD_BENCH is ON, use GoogleBenchmark +if(SOLO_BUILD_BENCH) + find_package(benchmark QUIET CONFIG) + if(benchmark_FOUND) + message(STATUS "GoogleBenchmark: found") + else() + if(SOLO_BUILD_DEPS) + message(STATUS "GoogleBenchmark: download ...") + solo_fetch_thirdparty_content(ExternalBenchmark) + else() + message(FATAL_ERROR "GoogleBenchmark: not found, please download and install manually") + endif() + endif() + + # Add source files to bench + set(SOLO_BENCH_FILES "") + # Test files in this directory + set(SOLO_BENCH_FILES ${SOLO_BENCH_FILES} + ${CMAKE_CURRENT_LIST_DIR}/bench.cpp + ${CMAKE_CURRENT_LIST_DIR}/ec_openssl_bench.cpp + ${CMAKE_CURRENT_LIST_DIR}/hash_bench.cpp + ${CMAKE_CURRENT_LIST_DIR}/prng_bench.cpp + ${CMAKE_CURRENT_LIST_DIR}/sampling_bench.cpp + ) + + set(CMAKE_CXX_LINK_EXECUTABLE "${CMAKE_CXX_LINK_EXECUTABLE} -ldl -lrt") + add_executable(solo_bench ${SOLO_BENCH_FILES}) + + if(TARGET PETAce-Solo::solo) + target_link_libraries(solo_bench PRIVATE PETAce-Solo::solo benchmark::benchmark) + elseif(TARGET PETAce-Solo::solo_shared) + target_link_libraries(solo_bench PRIVATE PETAce-Solo::solo_shared benchmark::benchmark) + else() + message(FATAL_ERROR "Cannot find target PETAce-Solo::solo or PETAce-Solo::solo_shared") + endif() +endif() diff --git a/bench/bench.cpp b/bench/bench.cpp new file mode 100644 index 0000000..93735e5 --- /dev/null +++ b/bench/bench.cpp @@ -0,0 +1,43 @@ +// Copyright 2023 TikTok Pte. Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "bench.h" + +#include "solo/solo.h" + +#define PETACE_REG_BENCH(category, name, func, ...) \ + benchmark::RegisterBenchmark( \ + (std::string(#category " / " #name)).c_str(), [=](benchmark::State& st) { func(st, __VA_ARGS__); }) \ + ->Unit(benchmark::kMicrosecond) \ + ->Iterations(10); + +int main(int argc, char** argv) { + PETACE_REG_BENCH(Hash, SHA_256, bm_hash_sha_256, petace::solo::Byte(0)); + PETACE_REG_BENCH(Hash, SHA3_256, bm_hash_sha3_256, petace::solo::Byte(0)); + PETACE_REG_BENCH(Hash, BLAKE2, bm_hash_blake2, petace::solo::Byte(0)); + PETACE_REG_BENCH(petace::solo::PRNG, SHAKE_128, bm_prng_shake_128, std::size_t(4096)); + PETACE_REG_BENCH(petace::solo::PRNG, BLAKE2X, bm_prng_blake2x, std::size_t(4096)); + PETACE_REG_BENCH(petace::solo::PRNG, AES_ECB_CTR, bm_prng_aes_ctr, std::size_t(4096)); + PETACE_REG_BENCH(Sampling, UNIFORM_BYTE_ARRAY_SHAKE_128, bm_sample_uniform_byte_array_shake_128, std::size_t(4096)); + PETACE_REG_BENCH(Sampling, UNIFORM_BYTE_ARRAY_BLAKE2X, bm_sample_uniform_byte_array_blake2x, std::size_t(4096)); + PETACE_REG_BENCH(Sampling, UNIFORM_BYTE_ARRAY_AES_ECB_CTR, bm_sample_uniform_byte_array_aes_ctr, std::size_t(4096)); + PETACE_REG_BENCH(EC_OpenSSL, hash_to_curve, bm_ec_hash_to_curve, petace::solo::Byte(0)); + PETACE_REG_BENCH(EC_OpenSSL, encrypt, bm_ec_encrypt, petace::solo::Byte(0)); + PETACE_REG_BENCH(EC_OpenSSL, decrypt, bm_ec_decrypt, petace::solo::Byte(0)); + PETACE_REG_BENCH(EC_OpenSSL, switch_key, bm_ec_switch_key, petace::solo::Byte(0)); + + benchmark::Initialize(&argc, argv); + + benchmark::RunSpecifiedBenchmarks(); +} diff --git a/bench/bench.h b/bench/bench.h new file mode 100644 index 0000000..f358c9e --- /dev/null +++ b/bench/bench.h @@ -0,0 +1,47 @@ +// Copyright 2023 TikTok Pte. Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#if (SOLO_COMPILER == SOLO_COMPILER_GCC) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wconversion" +#elif (SOLO_COMPILER == SOLO_COMPILER_CLANG) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wconversion" +#endif +#include "benchmark/benchmark.h" +#if (SOLO_COMPILER == SOLO_COMPILER_GCC) +#pragma GCC diagnostic pop +#elif (SOLO_COMPILER == SOLO_COMPILER_CLANG) +#pragma clang diagnostic pop +#endif + +#include "solo/solo.h" + +// Hash benchmark cases +void bm_hash_sha_256(benchmark::State& state, petace::solo::Byte value); +void bm_hash_sha3_256(benchmark::State& state, petace::solo::Byte value); +void bm_hash_blake2(benchmark::State& state, petace::solo::Byte value); +// PRNG benchmark cases +void bm_prng_shake_128(benchmark::State& state, std::size_t byte_count); +void bm_prng_blake2x(benchmark::State& state, std::size_t byte_count); +void bm_prng_aes_ctr(benchmark::State& state, std::size_t byte_count); +// Sampling benchmark cases +void bm_sample_uniform_byte_array_shake_128(benchmark::State& state, std::size_t byte_count); +void bm_sample_uniform_byte_array_blake2x(benchmark::State& state, std::size_t byte_count); +void bm_sample_uniform_byte_array_aes_ctr(benchmark::State& state, std::size_t byte_count); +// EC OpenSSL benchmark cases +void bm_ec_hash_to_curve(benchmark::State& state, petace::solo::Byte value); +void bm_ec_encrypt(benchmark::State& state, petace::solo::Byte value); +void bm_ec_decrypt(benchmark::State& state, petace::solo::Byte value); +void bm_ec_switch_key(benchmark::State& state, petace::solo::Byte value); diff --git a/bench/ec_openssl_bench.cpp b/bench/ec_openssl_bench.cpp new file mode 100644 index 0000000..5e1622f --- /dev/null +++ b/bench/ec_openssl_bench.cpp @@ -0,0 +1,100 @@ +// Copyright 2023 TikTok Pte. Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "bench.h" + +#include "solo/ec_openssl.h" + +void bm_ec_hash_to_curve(benchmark::State& state, petace::solo::Byte value) { + std::array input; + using EC = petace::solo::ECOpenSSL; + int curve_id = 415; + EC ec(curve_id, petace::solo::HashScheme::SHA_256); + EC::Point point(ec); + + for (auto _ : state) { + state.PauseTiming(); + for (auto& i : input) + i = value; + state.ResumeTiming(); + ec.hash_to_curve(input.data(), 64, point); + } +} + +void bm_ec_encrypt(benchmark::State& state, petace::solo::Byte value) { + std::array input; + for (auto& i : input) + i = value; + using EC = petace::solo::ECOpenSSL; + int curve_id = 415; + EC ec(curve_id, petace::solo::HashScheme::SHA_256); + EC::Point pt(ec); + ec.hash_to_curve(input.data(), 64, pt); + EC::Point ct(ec); + EC::SecretKey sk; + ec.create_secret_key(petace::solo::PRNGFactory(petace::solo::PRNGScheme::BLAKE2Xb).create(), sk); + + for (auto _ : state) { + state.PauseTiming(); + state.ResumeTiming(); + ec.encrypt(pt, sk, ct); + } +} + +void bm_ec_decrypt(benchmark::State& state, petace::solo::Byte value) { + std::array input; + for (auto& i : input) + i = value; + using EC = petace::solo::ECOpenSSL; + int curve_id = 415; + EC ec(curve_id, petace::solo::HashScheme::SHA_256); + EC::Point pt(ec); + ec.hash_to_curve(input.data(), 64, pt); + EC::Point ct(ec); + EC::SecretKey sk; + ec.create_secret_key(petace::solo::PRNGFactory(petace::solo::PRNGScheme::BLAKE2Xb).create(), sk); + EC::Point pt_other(ec); + ec.encrypt(pt, sk, ct); + + for (auto _ : state) { + state.PauseTiming(); + state.ResumeTiming(); + ec.decrypt(ct, sk, pt_other); + } +} + +void bm_ec_switch_key(benchmark::State& state, petace::solo::Byte value) { + std::array input; + for (auto& i : input) + i = value; + using EC = petace::solo::ECOpenSSL; + int curve_id = 415; + EC ec(curve_id, petace::solo::HashScheme::SHA_256); + EC::Point pt(ec); + ec.hash_to_curve(input.data(), 64, pt); + EC::Point ct(ec); + EC::SecretKey sk; + auto prng = petace::solo::PRNGFactory(petace::solo::PRNGScheme::BLAKE2Xb).create(); + ec.create_secret_key(prng, sk); + EC::SecretKey sk_new; + ec.create_secret_key(prng, sk_new); + ec.encrypt(pt, sk, ct); + EC::Point ct_other(ec); + + for (auto _ : state) { + state.PauseTiming(); + state.ResumeTiming(); + ec.switch_key(ct, sk, sk_new, ct_other); + } +} diff --git a/bench/hash_bench.cpp b/bench/hash_bench.cpp new file mode 100644 index 0000000..0ddca4d --- /dev/null +++ b/bench/hash_bench.cpp @@ -0,0 +1,56 @@ +// Copyright 2023 TikTok Pte. Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "bench.h" + +#include "solo/hash.h" + +void bm_hash_sha_256(benchmark::State& state, petace::solo::Byte value) { + petace::solo::Byte input[64]; + auto hash = petace::solo::Hash::create(petace::solo::HashScheme::SHA_256); + std::vector block(hash->hash_byte_count(), value); + for (auto _ : state) { + state.PauseTiming(); + for (auto& i : block) + i = value; + state.ResumeTiming(); + hash->compute(input, 64, block.data(), block.size()); + } +} + +void bm_hash_sha3_256(benchmark::State& state, petace::solo::Byte value) { + petace::solo::Byte input[64]; + auto hash = petace::solo::Hash::create(petace::solo::HashScheme::SHA3_256); + std::vector block(hash->hash_byte_count(), value); + for (auto _ : state) { + state.PauseTiming(); + for (auto& i : block) + i = value; + state.ResumeTiming(); + hash->compute(input, 64, block.data(), block.size()); + } +} + +void bm_hash_blake2(benchmark::State& state, petace::solo::Byte value) { + petace::solo::Byte input[64]; + auto hash = petace::solo::Hash::create(petace::solo::HashScheme::BLAKE2b); + std::vector block(hash->hash_byte_count(), value); + for (auto _ : state) { + state.PauseTiming(); + for (auto& i : block) + i = value; + state.ResumeTiming(); + hash->compute(input, 64, block.data(), block.size()); + } +} diff --git a/bench/prng_bench.cpp b/bench/prng_bench.cpp new file mode 100644 index 0000000..1f7538a --- /dev/null +++ b/bench/prng_bench.cpp @@ -0,0 +1,55 @@ +// Copyright 2023 TikTok Pte. Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "bench.h" + +#include "solo/prng.h" + +static const std::size_t kSeedByteCount = 16; + +void bm_prng_shake_128(benchmark::State& state, std::size_t byte_count) { + std::vector output(byte_count); + petace::solo::PRNGFactory prng_factory(petace::solo::PRNGScheme::SHAKE_128, kSeedByteCount); + std::shared_ptr prng = prng_factory.create(); + for (auto _ : state) { + state.PauseTiming(); + prng = prng_factory.create(); + state.ResumeTiming(); + prng->generate(byte_count, output.data()); + } +} + +void bm_prng_blake2x(benchmark::State& state, std::size_t byte_count) { + std::vector output(byte_count); + petace::solo::PRNGFactory prng_factory(petace::solo::PRNGScheme::BLAKE2Xb, kSeedByteCount); + std::shared_ptr prng = prng_factory.create(); + for (auto _ : state) { + state.PauseTiming(); + prng = prng_factory.create(); + state.ResumeTiming(); + prng->generate(byte_count, output.data()); + } +} + +void bm_prng_aes_ctr(benchmark::State& state, std::size_t byte_count) { + std::vector output(byte_count); + petace::solo::PRNGFactory prng_factory(petace::solo::PRNGScheme::AES_ECB_CTR, kSeedByteCount); + std::shared_ptr prng = prng_factory.create(); + for (auto _ : state) { + state.PauseTiming(); + prng = prng_factory.create(); + state.ResumeTiming(); + prng->generate(byte_count, output.data()); + } +} diff --git a/bench/sampling_bench.cpp b/bench/sampling_bench.cpp new file mode 100644 index 0000000..ea735e4 --- /dev/null +++ b/bench/sampling_bench.cpp @@ -0,0 +1,56 @@ +// Copyright 2023 TikTok Pte. Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "bench.h" + +#include "solo/prng.h" +#include "solo/sampling.h" + +static const std::size_t kSeedByteCount = 16; + +void bm_sample_uniform_byte_array_shake_128(benchmark::State& state, std::size_t byte_count) { + std::vector output(byte_count); + petace::solo::PRNGFactory prng_factory(petace::solo::PRNGScheme::SHAKE_128, kSeedByteCount); + std::shared_ptr prng = prng_factory.create(); + for (auto _ : state) { + state.PauseTiming(); + prng = prng_factory.create(); + state.ResumeTiming(); + sample_uniform_byte_array(*prng, byte_count, output.data()); + } +} + +void bm_sample_uniform_byte_array_blake2x(benchmark::State& state, std::size_t byte_count) { + std::vector output(byte_count); + petace::solo::PRNGFactory prng_factory(petace::solo::PRNGScheme::BLAKE2Xb, kSeedByteCount); + std::shared_ptr prng = prng_factory.create(); + for (auto _ : state) { + state.PauseTiming(); + prng = prng_factory.create(); + state.ResumeTiming(); + sample_uniform_byte_array(*prng, byte_count, output.data()); + } +} + +void bm_sample_uniform_byte_array_aes_ctr(benchmark::State& state, std::size_t byte_count) { + std::vector output(byte_count); + petace::solo::PRNGFactory prng_factory(petace::solo::PRNGScheme::AES_ECB_CTR, kSeedByteCount); + std::shared_ptr prng = prng_factory.create(); + for (auto _ : state) { + state.PauseTiming(); + prng = prng_factory.create(); + state.ResumeTiming(); + sample_uniform_byte_array(*prng, byte_count, output.data()); + } +} diff --git a/cmake/EnableCXX17.cmake b/cmake/EnableCXX17.cmake new file mode 100644 index 0000000..a75a04d --- /dev/null +++ b/cmake/EnableCXX17.cmake @@ -0,0 +1,49 @@ +# Copyright 2023 TikTok Pte. Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set(SOLO_USE_STD_BYTE ${SOLO_USE_CXX17}) +set(SOLO_USE_SHARED_MUTEX ${SOLO_USE_CXX17}) +set(SOLO_USE_IF_CONSTEXPR ${SOLO_USE_CXX17}) +set(SOLO_USE_MAYBE_UNUSED ${SOLO_USE_CXX17}) +set(SOLO_USE_NODISCARD ${SOLO_USE_CXX17}) + +if(SOLO_USE_CXX17) + set(SOLO_LANG_FLAG "-std=c++17") +else() + set(SOLO_LANG_FLAG "-std=c++14") +endif() + +set(SOLO_USE_STD_FOR_EACH_N ${SOLO_USE_CXX17}) +# In some non-MSVC compilers std::for_each_n is not available even when compiling as C++17 +if(SOLO_USE_STD_FOR_EACH_N) + CMAKE_PUSH_CHECK_STATE(RESET) + set(CMAKE_REQUIRED_QUIET TRUE) + if(NOT MSVC) + set(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} -O0 ${SOLO_LANG_FLAG}") + CHECK_CXX_SOURCE_COMPILES(" + #include + int main() { + int a[1]{ 0 }; + volatile auto fun = std::for_each_n(a, 1, [](auto b) {}); + return 0; + }" + USE_STD_FOR_EACH_N + ) + if(NOT USE_STD_FOR_EACH_N EQUAL 1) + set(SOLO_USE_STD_FOR_EACH_N OFF) + endif() + unset(USE_STD_FOR_EACH_N CACHE) + endif() + CMAKE_POP_CHECK_STATE() +endif() diff --git a/cmake/EnableDebugFlags.cmake b/cmake/EnableDebugFlags.cmake new file mode 100644 index 0000000..f1e51da --- /dev/null +++ b/cmake/EnableDebugFlags.cmake @@ -0,0 +1,33 @@ +# Copyright 2023 TikTok Pte. Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +include(CheckCXXCompilerFlag) +function(solo_add_compiler_flag flag) + string(FIND "${CMAKE_CXX_FLAGS}" "${flag}" flag_already_set) + if(flag_already_set EQUAL -1) + message(STATUS "Adding CXX compiler flag: ${flag} ...") + CHECK_CXX_COMPILER_FLAG("${flag}" flag_supported) + if(flag_supported) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${flag}" PARENT_SCOPE) + endif() + unset(flag_supported CACHE) + endif() +endfunction() +if(NOT MSVC AND SOLO_DEBUG) + solo_add_compiler_flag("-Wall") + solo_add_compiler_flag("-Wextra") + solo_add_compiler_flag("-Wconversion") + solo_add_compiler_flag("-Wshadow") + solo_add_compiler_flag("-pedantic") +endif() diff --git a/cmake/ExternalBenchmark.cmake b/cmake/ExternalBenchmark.cmake new file mode 100644 index 0000000..cda74f6 --- /dev/null +++ b/cmake/ExternalBenchmark.cmake @@ -0,0 +1,61 @@ +# Copyright 2023 TikTok Pte. Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +FetchContent_Declare( + benchmark + GIT_REPOSITORY https://github.com/google/benchmark.git + GIT_TAG 0d98dba29d66e93259db7daa53a9327df767a415 # 1.6.1 +) +FetchContent_GetProperties(benchmark) + +if(NOT benchmark) + FetchContent_Populate(benchmark) + + set(LLVMAR_EXECUTABLE ${CMAKE_AR}) + set(LLVMNM_EXECUTABLE ${CMAKE_NM}) + set(LLVMRANLIB_EXECUTABLE ${CMAKE_RANLIB}) + set(LLVM_FILECHECK_EXE ${CMAKE_CXX_COMPILER_AR}/../FileCheck) + set(BENCHMARK_ENABLE_GTEST_TESTS OFF CACHE BOOL "" FORCE) + set(BENCHMARK_ENABLE_INSTALL OFF CACHE BOOL "" FORCE) + set(BENCHMARK_ENABLE_TESTING OFF CACHE BOOL "" FORCE) + set(BENCHMARK_ENABLE_LTO OFF CACHE BOOL "" FORCE) + mark_as_advanced(LIBRT) + mark_as_advanced(LLVM_FILECHECK_EXE) + mark_as_advanced(BENCHMARK_BUILD_32_BITS) + mark_as_advanced(BENCHMARK_DOWNLOAD_DEPENDENCIES) + mark_as_advanced(BENCHMARK_ENABLE_ASSEMBLY_TESTS) + mark_as_advanced(BENCHMARK_ENABLE_EXCEPTIONS) + mark_as_advanced(BENCHMARK_ENABLE_GTEST_TESTS) + mark_as_advanced(BENCHMARK_ENABLE_INSTALL) + mark_as_advanced(BENCHMARK_ENABLE_LTO) + mark_as_advanced(BENCHMARK_ENABLE_TESTING) + mark_as_advanced(BENCHMARK_USE_LIBCXX) + mark_as_advanced(FETCHCONTENT_SOURCE_DIR_BENCHMARK) + mark_as_advanced(FETCHCONTENT_UPDATES_DISCONNECTED_BENCHMARK) + + if(NOT WIN32) + # Google Benchmark contains unsafe conversions so force -Wno-conversion temporarily + set(OLD_CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS}) + set(CMAKE_CXX_FLAGS "${OLD_CMAKE_CXX_FLAGS} -Wno-conversion") + endif() + + add_subdirectory( + ${benchmark_SOURCE_DIR} + ${THIRDPARTY_BINARY_DIR}/benchmark-src + EXCLUDE_FROM_ALL) + + if(NOT WIN32) + set(CMAKE_CXX_FLAGS ${OLD_CMAKE_CXX_FLAGS}) + endif() +endif() diff --git a/cmake/ExternalGTest.cmake b/cmake/ExternalGTest.cmake new file mode 100644 index 0000000..20b4a9e --- /dev/null +++ b/cmake/ExternalGTest.cmake @@ -0,0 +1,37 @@ +# Copyright 2023 TikTok Pte. Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +FetchContent_Declare( + googletest + GIT_REPOSITORY https://github.com/google/googletest.git + GIT_TAG 58d77fa8070e8cec2dc1ed015d66b454c8d78850 # 1.12.1 +) +FetchContent_GetProperties(googletest) + +if(NOT googletest_POPULATED) + FetchContent_Populate(googletest) + + set(BUILD_GMOCK OFF CACHE BOOL "" FORCE) + set(INSTALL_GTEST OFF CACHE BOOL "" FORCE) + set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) + mark_as_advanced(BUILD_GMOCK) + mark_as_advanced(INSTALL_GTEST) + mark_as_advanced(FETCHCONTENT_SOURCE_DIR_GOOGLETEST) + mark_as_advanced(FETCHCONTENT_UPDATES_DISCONNECTED_GOOGLETEST) + + add_subdirectory( + ${googletest_SOURCE_DIR} + ${THIRDPARTY_BINARY_DIR}/googletest-src + EXCLUDE_FROM_ALL) +endif() diff --git a/cmake/ExternalOpenSSL.cmake b/cmake/ExternalOpenSSL.cmake new file mode 100644 index 0000000..699d680 --- /dev/null +++ b/cmake/ExternalOpenSSL.cmake @@ -0,0 +1,53 @@ +# Copyright 2023 TikTok Pte. Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +include(ExternalProject) + +set(OPENSSL_SOURCES_DIR ${SOLO_THIRDPARTY_DIR}/openssl-src) +set(OPENSSL_INSTALL_DIR "${OPENSSL_SOURCES_DIR}/install") +set(OPENSSL_INCLUDE_DIR "${OPENSSL_INSTALL_DIR}/include") +set(OPENSSL_NAME "openssl") + +include(ProcessorCount) +ProcessorCount(NUM_OF_PROCESSOR) + +if((NOT DEFINED OPENSSL_URL) OR (NOT DEFINED OPENSSL_VER)) + set(OPENSSL_URL "https://www.openssl.org/source/old/1.1.1/openssl-1.1.1d.tar.gz" CACHE STRING "" FORCE) + set(OPENSSL_VER "openssl-1.1.1d" CACHE STRING "" FORCE) +endif() + +ExternalProject_Add( + extern_openssl + PREFIX ${OPENSSL_SOURCES_DIR} + DOWNLOAD_COMMAND wget --no-check-certificate ${OPENSSL_URL} -c -q -O ${OPENSSL_NAME}.tar.gz + && tar -xvf ${OPENSSL_NAME}.tar.gz + SOURCE_DIR ${OPENSSL_SOURCES_DIR}/src/${OPENSSL_VER} + CONFIGURE_COMMAND ./config no-shared -fPIC --prefix=${OPENSSL_INSTALL_DIR} ${OPENSSL_ASM_OPTION} + BUILD_COMMAND make depend -j ${NUM_OF_PROCESSOR} && make -j ${NUM_OF_PROCESSOR} + INSTALL_COMMAND make install_sw + BUILD_IN_SOURCE 1 +) + +set(OPENSSL_CRYPTO_LIBRARY "${OPENSSL_INSTALL_DIR}/lib/libcrypto.a") +set(OPENSSL_SSL_LIBRARY "${OPENSSL_INSTALL_DIR}/lib/libssl.a") + +add_library(crypto STATIC IMPORTED GLOBAL) +add_library(ssl STATIC IMPORTED GLOBAL) +set_property(TARGET crypto PROPERTY IMPORTED_LOCATION ${OPENSSL_CRYPTO_LIBRARY}) +set_property(TARGET ssl PROPERTY IMPORTED_LOCATION ${OPENSSL_SSL_LIBRARY}) + +add_dependencies(crypto extern_openssl) +add_dependencies(ssl extern_openssl) + +include_directories(${OPENSSL_INCLUDE_DIR}) diff --git a/cmake/PETAce-SoloConfig.cmake.in b/cmake/PETAce-SoloConfig.cmake.in new file mode 100644 index 0000000..5b49f00 --- /dev/null +++ b/cmake/PETAce-SoloConfig.cmake.in @@ -0,0 +1,81 @@ +# Copyright 2023 TikTok Pte. Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +@PACKAGE_INIT@ + +include(CMakeFindDependencyMacro) + +macro(solo_find_dependency dep) + find_dependency(${dep}) + if(NOT ${dep}_FOUND) + if(NOT PETAce-Solo_FIND_QUIETLY) + message(WARNING "Could not find dependency `${dep}` required by this configuration") + endif() + set(PETAce-Solo_FOUND FALSE) + return() + endif() +endmacro() + +set(PETAce-Solo_FOUND FALSE) +set(PETAce-Solo_STATIC_FOUND FALSE) +set(PETAce-Solo_SHARED_FOUND FALSE) + +set(SOLO_DEBUG @SOLO_DEBUG@) +set(SOLO_CARRY_OPENSSL @SOLO_CARRY_OPENSSL@) + +# Add the current directory to the module search path +list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}) + +set(CMAKE_THREAD_PREFER_PTHREAD TRUE) +set(THREADS_PREFER_PTHREAD_FLAG TRUE) +solo_find_dependency(Threads) +if (NOT SOLO_CARRY_OPENSSL) + solo_find_dependency(OpenSSL) +endif() + +include(${CMAKE_CURRENT_LIST_DIR}/PETAce-SoloTargets.cmake) + +if(TARGET PETAce-Solo::solo) + set(PETAce-Solo_FOUND TRUE) + set(PETAce-Solo_STATIC_FOUND TRUE) +endif() + +if(TARGET PETAce-Solo::solo_shared) + set(PETAce-Solo_FOUND TRUE) + set(PETAce-Solo_SHARED_FOUND TRUE) +endif() + +if(PETAce-Solo_FOUND) + if(NOT PETAce-Solo_FIND_QUIETLY) + message(STATUS "PETAce-Solo -> Version ${PETAce-Solo_VERSION} detected") + endif() + if(SOLO_DEBUG AND NOT PETAce-Solo_FIND_QUIETLY) + message(STATUS "Performance warning: PETAce-Solo compiled in debug mode") + endif() + set(PETAce-Solo_TARGETS_AVAILABLE "PETAce-Solo -> Targets available:") + + if(PETAce-Solo_STATIC_FOUND) + string(APPEND PETAce-Solo_TARGETS_AVAILABLE " PETAce-Solo::solo") + endif() + if(PETAce-Solo_SHARED_FOUND) + string(APPEND PETAce-Solo_TARGETS_AVAILABLE " PETAce-Solo::solo_shared") + endif() + if(NOT PETAce-Solo_FIND_QUIETLY) + message(STATUS ${PETAce-Solo_TARGETS_AVAILABLE}) + endif() +else() + if(NOT PETAce-Solo_FIND_QUIETLY) + message(STATUS "PETAce-Solo -> NOT FOUND") + endif() +endif() diff --git a/cmake/SoloCustomMacros.cmake b/cmake/SoloCustomMacros.cmake new file mode 100644 index 0000000..18b660c --- /dev/null +++ b/cmake/SoloCustomMacros.cmake @@ -0,0 +1,53 @@ +# Copyright 2023 TikTok Pte. Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Manually combine archives, using ${CMAKE_LIBRARY_OUTPUT_DIRECTORY} to keep temporary files. +macro(solo_combine_archives target dependency) + if(MSVC) + add_custom_command(TARGET ${target} POST_BUILD + COMMAND lib.exe /OUT:$ $ $ + DEPENDS $ $ + WORKING_DIRECTORY ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}) + else() + if(CMAKE_HOST_WIN32) + get_filename_component(CXX_DIR "${CMAKE_CXX_COMPILER}" DIRECTORY) + set(AR_CMD_PATH "${CXX_DIR}/llvm-ar.exe") + file(TO_NATIVE_PATH "${AR_CMD_PATH}" AR_CMD_PATH) + set(DEL_CMD "del") + set(DEL_CMD_OPTS "") + else() + set(AR_CMD_PATH "ar") + set(DEL_CMD "rm") + set(DEL_CMD_OPTS "-rf") + endif() + if(EMSCRIPTEN) + set(AR_CMD_PATH "emar") + endif() + add_custom_command(TARGET ${target} POST_BUILD + COMMAND "${AR_CMD_PATH}" x $ + COMMAND "${AR_CMD_PATH}" x $ + COMMAND "${AR_CMD_PATH}" rcs $ *.o + COMMAND ${DEL_CMD} ${DEL_CMD_OPTS} *.o + WORKING_DIRECTORY ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}) + endif() +endmacro() + +# Fetch thirdparty content specified by content_file +macro(solo_fetch_thirdparty_content content_file) + set(SOLO_FETCHCONTENT_BASE_DIR_OLD ${FETCHCONTENT_BASE_DIR}) + set(FETCHCONTENT_BASE_DIR ${SOLO_THIRDPARTY_DIR} CACHE STRING "" FORCE) + include(${content_file}) + set(FETCHCONTENT_BASE_DIR ${SOLO_FETCHCONTENT_BASE_DIR_OLD} CACHE STRING "" FORCE) + unset(SOLO_FETCHCONTENT_BASE_DIR_OLD) +endmacro() diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt new file mode 100644 index 0000000..db5bdf1 --- /dev/null +++ b/example/CMakeLists.txt @@ -0,0 +1,48 @@ +# Copyright 2023 TikTok Pte. Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +cmake_minimum_required(VERSION 3.14) + +project(SOLOExample VERSION 0.1.0 LANGUAGES CXX) + +# If not called from root CMakeLists.txt +if(NOT DEFINED SOLO_BUILD_EXAMPLE) + set(SOLO_BUILD_EXAMPLE ON) + + # Import PETAce-Solo + find_package(PETAce-Solo 0.1.0 EXACT REQUIRED) + + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/bin) +endif() + +if(SOLO_BUILD_EXAMPLE) + # Add source files to example + set(SOLO_EXAMPLE_FILES "") + # Test files in this directory + set(SOLO_EXAMPLE_FILES ${SOLO_EXAMPLE_FILES} + ${CMAKE_CURRENT_LIST_DIR}/example.cpp + ) + + set(CMAKE_CXX_LINK_EXECUTABLE "${CMAKE_CXX_LINK_EXECUTABLE} -ldl -lrt") + + add_executable(solo_example ${SOLO_EXAMPLE_FILES}) + + if(TARGET PETAce-Solo::solo) + target_link_libraries(solo_example PRIVATE PETAce-Solo::solo) + elseif(TARGET PETAce-Solo::solo_shared) + target_link_libraries(solo_example PRIVATE PETAce-Solo::solo_shared) + else() + message(FATAL_ERROR "Cannot find target PETAce-Solo::solo or PETAce-Solo::solo_shared") + endif() +endif() diff --git a/example/example.cpp b/example/example.cpp new file mode 100644 index 0000000..3b0268c --- /dev/null +++ b/example/example.cpp @@ -0,0 +1,140 @@ +// Copyright 2023 TikTok Pte. Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include + +#include "solo/solo.h" + +static void print_header(std::string name) { + if (!name.empty()) { + std::size_t name_length = name.length(); + std::size_t header_length = name_length + 2 * 10; + std::string header_top = "+" + std::string(header_length - 2, '-') + "+"; + std::string header_middle = "|" + std::string(9, ' ') + name + std::string(9, ' ') + "|"; + std::cout << std::endl << header_top << std::endl << header_middle << std::endl << header_top << std::endl; + } +} + +static std::string byte_array_to_hex_string(const petace::solo::Byte* input, std::size_t input_byte_count) { + std::stringstream ss; + ss << std::hex << std::setfill('0'); + for (std::size_t i = 0; i < input_byte_count; i++) { + ss << std::hex << std::setw(2) << static_cast(input[i]); + } + return ss.str(); +} + +void example_hash() { + print_header("Example: Hash"); + + std::cout << "Use SHA-256 as our hash function" << std::endl; + auto hash = petace::solo::Hash::create(petace::solo::HashScheme::SHA_256); + + std::string input_text = "Use SHA-256 as our hash function"; + std::cout << " Input text: \"" << input_text << "\"" << std::endl; + std::array output_digest; + hash->compute(reinterpret_cast(input_text.c_str()), input_text.length(), + output_digest.data(), output_digest.size()); + std::cout << " Output digest: \"" << byte_array_to_hex_string(output_digest.data(), output_digest.size()) << "\"" + << std::endl; +} + +void example_prng() { + print_header("Example: PRNG"); + + std::vector seed(32); + petace::solo::PRNG::get_random_byte_array(seed.size(), seed.data()); + std::cout << "Generate a random 256-bit seed: \"" << byte_array_to_hex_string(seed.data(), seed.size()) << "\"" + << std::endl; + + std::cout << std::endl; + std::cout << "Use BLAKE2Xb to create our PRNG" << std::endl; + petace::solo::PRNGFactory prng_factory(petace::solo::PRNGScheme::BLAKE2Xb, seed.size()); + auto prng = prng_factory.create(seed); + + petace::solo::Byte output[32]; + prng->generate(32, output); + std::cout << " Generate 32 random bytes: \"" << byte_array_to_hex_string(output, 32) << "\"" << std::endl; + + prng->generate(16, output); + std::cout << " Generate 16 random bytes: \"" << byte_array_to_hex_string(output, 16) << "\"" << std::endl; + + std::cout << std::endl; + std::cout << "Re-create another PRNG from the same seed..." << std::endl; + auto prng_other = prng_factory.create(seed); + + prng_other->generate(32, output); + std::cout << " Generate 32 random bytes: \"" << byte_array_to_hex_string(output, 32) << "\"" << std::endl; + + prng_other->generate(16, output); + std::cout << " Generate 16 random bytes: \"" << byte_array_to_hex_string(output, 16) << "\"" << std::endl; +} + +void example_sampling() { + print_header("Example: Sampling"); + + std::vector seed(16); + petace::solo::PRNG::get_random_byte_array(seed.size(), seed.data()); + std::cout << "Generate a random 128-bit seed: \"" << byte_array_to_hex_string(seed.data(), seed.size()) << "\"" + << std::endl; + + std::cout << std::endl; + std::cout << "Use AES_ECB_CTR to create our PRNG" << std::endl; + petace::solo::PRNGFactory prng_factory(petace::solo::PRNGScheme::AES_ECB_CTR, seed.size()); + auto prng = prng_factory.create(seed); + + std::cout << " Generate a random 32-bit unsigned integer: " << sample_uniform_uint32(*prng) << std::endl; + std::cout << " Generate a random 64-bit unsigned integer: " << sample_uniform_uint64(*prng) << std::endl; + + std::cout << std::endl; + std::cout << "Re-create another PRNG from the same seed..." << std::endl; + auto prng_other = prng_factory.create(seed); + + std::cout << " Generate a random 32-bit unsigned integer: " << sample_uniform_uint32(*prng_other) << std::endl; + std::cout << " Generate a random 64-bit unsigned integer: " << sample_uniform_uint64(*prng_other) << std::endl; +} + +void example_ec_openssl() { + print_header("Example: EC OpenSSL"); + + using EC = petace::solo::ECOpenSSL; + int curve_id = NID_X9_62_prime256v1; + std::cout << "Use curve NID_X9_62_prime256v1 and BLAKE2b" << std::endl; + EC ec(curve_id, petace::solo::HashScheme::BLAKE2b); + + std::string input_text = "Use curve NID_X9_62_prime256v1 and BLAKE2b"; + std::cout << " Input text: \"" << input_text << "\"" << std::endl; + + EC::Point output_point(ec); + ec.hash_to_curve( + reinterpret_cast(input_text.c_str()), input_text.length(), output_point); + std::size_t output_byte_count = ec.point_to_bytes(output_point, 0, nullptr); + std::vector output_bytes(output_byte_count); + ec.point_to_bytes(output_point, output_byte_count, output_bytes.data()); + std::cout << " Output point: \"" << byte_array_to_hex_string(output_bytes.data(), output_bytes.size()) << "\"" + << std::endl; +} + +int main() { + std::cout << "PETAce-Solo version: " << SOLO_VERSION << std::endl; + + example_hash(); + example_prng(); + example_sampling(); + example_ec_openssl(); + + return 0; +} diff --git a/licenses/LICENSE-BLAKE2.txt b/licenses/LICENSE-BLAKE2.txt new file mode 100644 index 0000000..0e259d4 --- /dev/null +++ b/licenses/LICENSE-BLAKE2.txt @@ -0,0 +1,121 @@ +Creative Commons Legal Code + +CC0 1.0 Universal + + CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE + LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN + ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS + INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES + REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS + PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM + THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED + HEREUNDER. + +Statement of Purpose + +The laws of most jurisdictions throughout the world automatically confer +exclusive Copyright and Related Rights (defined below) upon the creator +and subsequent owner(s) (each and all, an "owner") of an original work of +authorship and/or a database (each, a "Work"). + +Certain owners wish to permanently relinquish those rights to a Work for +the purpose of contributing to a commons of creative, cultural and +scientific works ("Commons") that the public can reliably and without fear +of later claims of infringement build upon, modify, incorporate in other +works, reuse and redistribute as freely as possible in any form whatsoever +and for any purposes, including without limitation commercial purposes. +These owners may contribute to the Commons to promote the ideal of a free +culture and the further production of creative, cultural and scientific +works, or to gain reputation or greater distribution for their Work in +part through the use and efforts of others. + +For these and/or other purposes and motivations, and without any +expectation of additional consideration or compensation, the person +associating CC0 with a Work (the "Affirmer"), to the extent that he or she +is an owner of Copyright and Related Rights in the Work, voluntarily +elects to apply CC0 to the Work and publicly distribute the Work under its +terms, with knowledge of his or her Copyright and Related Rights in the +Work and the meaning and intended legal effect of CC0 on those rights. + +1. Copyright and Related Rights. A Work made available under CC0 may be +protected by copyright and related or neighboring rights ("Copyright and +Related Rights"). Copyright and Related Rights include, but are not +limited to, the following: + + i. the right to reproduce, adapt, distribute, perform, display, + communicate, and translate a Work; + ii. moral rights retained by the original author(s) and/or performer(s); +iii. publicity and privacy rights pertaining to a person's image or + likeness depicted in a Work; + iv. rights protecting against unfair competition in regards to a Work, + subject to the limitations in paragraph 4(a), below; + v. rights protecting the extraction, dissemination, use and reuse of data + in a Work; + vi. database rights (such as those arising under Directive 96/9/EC of the + European Parliament and of the Council of 11 March 1996 on the legal + protection of databases, and under any national implementation + thereof, including any amended or successor version of such + directive); and +vii. other similar, equivalent or corresponding rights throughout the + world based on applicable law or treaty, and any national + implementations thereof. + +2. Waiver. To the greatest extent permitted by, but not in contravention +of, applicable law, Affirmer hereby overtly, fully, permanently, +irrevocably and unconditionally waives, abandons, and surrenders all of +Affirmer's Copyright and Related Rights and associated claims and causes +of action, whether now known or unknown (including existing as well as +future claims and causes of action), in the Work (i) in all territories +worldwide, (ii) for the maximum duration provided by applicable law or +treaty (including future time extensions), (iii) in any current or future +medium and for any number of copies, and (iv) for any purpose whatsoever, +including without limitation commercial, advertising or promotional +purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each +member of the public at large and to the detriment of Affirmer's heirs and +successors, fully intending that such Waiver shall not be subject to +revocation, rescission, cancellation, termination, or any other legal or +equitable action to disrupt the quiet enjoyment of the Work by the public +as contemplated by Affirmer's express Statement of Purpose. + +3. Public License Fallback. Should any part of the Waiver for any reason +be judged legally invalid or ineffective under applicable law, then the +Waiver shall be preserved to the maximum extent permitted taking into +account Affirmer's express Statement of Purpose. In addition, to the +extent the Waiver is so judged Affirmer hereby grants to each affected +person a royalty-free, non transferable, non sublicensable, non exclusive, +irrevocable and unconditional license to exercise Affirmer's Copyright and +Related Rights in the Work (i) in all territories worldwide, (ii) for the +maximum duration provided by applicable law or treaty (including future +time extensions), (iii) in any current or future medium and for any number +of copies, and (iv) for any purpose whatsoever, including without +limitation commercial, advertising or promotional purposes (the +"License"). The License shall be deemed effective as of the date CC0 was +applied by Affirmer to the Work. Should any part of the License for any +reason be judged legally invalid or ineffective under applicable law, such +partial invalidity or ineffectiveness shall not invalidate the remainder +of the License, and in such case Affirmer hereby affirms that he or she +will not (i) exercise any of his or her remaining Copyright and Related +Rights in the Work or (ii) assert any associated claims and causes of +action with respect to the Work, in either case contrary to Affirmer's +express Statement of Purpose. + +4. Limitations and Disclaimers. + + a. No trademark or patent rights held by Affirmer are waived, abandoned, + surrendered, licensed or otherwise affected by this document. + b. Affirmer offers the Work as-is and makes no representations or + warranties of any kind concerning the Work, express, implied, + statutory or otherwise, including without limitation warranties of + title, merchantability, fitness for a particular purpose, non + infringement, or the absence of latent or other defects, accuracy, or + the present or absence of errors, whether or not discoverable, all to + the greatest extent permissible under applicable law. + c. Affirmer disclaims responsibility for clearing rights of other persons + that may apply to the Work or any use thereof, including without + limitation any person's Copyright and Related Rights in the Work. + Further, Affirmer disclaims responsibility for obtaining any necessary + consents, permissions or other rights required for any use of the + Work. + d. Affirmer understands and acknowledges that Creative Commons is not a + party to this document and has no duty or obligation with respect to + this CC0 or use of the Work. diff --git a/src/solo/CMakeLists.txt b/src/solo/CMakeLists.txt new file mode 100644 index 0000000..79a5d28 --- /dev/null +++ b/src/solo/CMakeLists.txt @@ -0,0 +1,37 @@ +# Copyright 2023 TikTok Pte. Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Source files in this directory +set(SOLO_SOURCE_FILES ${SOLO_SOURCE_FILES} + ${CMAKE_CURRENT_LIST_DIR}/ec_openssl.cpp + ${CMAKE_CURRENT_LIST_DIR}/hash.cpp + ${CMAKE_CURRENT_LIST_DIR}/prng.cpp + ${CMAKE_CURRENT_LIST_DIR}/sampling.cpp +) + +# Add header files for installation +install( + FILES + ${CMAKE_CURRENT_LIST_DIR}/ec_openssl.h + ${CMAKE_CURRENT_LIST_DIR}/hash.h + ${CMAKE_CURRENT_LIST_DIR}/prng.h + ${CMAKE_CURRENT_LIST_DIR}/sampling.h + ${CMAKE_CURRENT_LIST_DIR}/solo.h + DESTINATION + ${SOLO_INCLUDES_INSTALL_DIR}/solo +) + +add_subdirectory(util) + +set(SOLO_SOURCE_FILES ${SOLO_SOURCE_FILES} PARENT_SCOPE) diff --git a/src/solo/ec_openssl.cpp b/src/solo/ec_openssl.cpp new file mode 100644 index 0000000..c56ff55 --- /dev/null +++ b/src/solo/ec_openssl.cpp @@ -0,0 +1,431 @@ +// Copyright 2023 TikTok Pte. Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "solo/ec_openssl.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "openssl/bn.h" +#include "openssl/ec.h" +#include "openssl/err.h" + +#include "solo/hash.h" +#include "solo/prng.h" +#include "solo/util/defines.h" + +namespace petace { +namespace solo { + +ECOpenSSL::UInt& ECOpenSSL::UInt::operator=(const UInt& copy) { + if (BN_copy(data_, copy.data_) == nullptr) { + throw std::runtime_error("openssl error: " + std::to_string(ERR_get_error())); + } + return *this; +} + +ECOpenSSL::Point& ECOpenSSL::Point::operator=(const Point& copy) { + if (EC_POINT_copy(data_, copy.data_) == 0) { + throw std::runtime_error("openssl error: " + std::to_string(ERR_get_error())); + } + return *this; +} + +ECOpenSSL::ECOpenSSL(int curve_id, HashScheme hash_scheme) + : group_(EC_GROUP_new_by_curve_name(curve_id)), + p_(BN_new()), + a_(BN_new()), + b_(BN_new()), + order_(BN_new()), + p_minus_one_over_two_(BN_new()), + three_(BN_new()), + hash_factory_(HashFactory::create_factory(hash_scheme)) { + if (group_ == nullptr || p_ == nullptr || a_ == nullptr || b_ == nullptr || order_ == nullptr || + p_minus_one_over_two_ == nullptr || three_ == nullptr || hash_factory_ == nullptr) { + throw std::runtime_error("openssl error: " + std::to_string(ERR_get_error())); + } + if (EC_GROUP_get_curve(group_, p_, a_, b_, NULL) == 0) { + throw std::runtime_error("openssl error: " + std::to_string(ERR_get_error())); + } + if (EC_GROUP_get_order(group_, order_, NULL) == 0) { + throw std::runtime_error("openssl error: " + std::to_string(ERR_get_error())); + } + + if (BN_copy(p_minus_one_over_two_, p_) == nullptr) { + throw std::runtime_error("openssl error: " + std::to_string(ERR_get_error())); + } + if (BN_sub_word(p_minus_one_over_two_, 1) == 0) { + throw std::runtime_error("openssl error: " + std::to_string(ERR_get_error())); + } + if (BN_rshift1(p_minus_one_over_two_, p_minus_one_over_two_) == 0) { + throw std::runtime_error("openssl error: " + std::to_string(ERR_get_error())); + } + + if (BN_set_word(three_, 3) == 0) { + throw std::runtime_error("openssl error: " + std::to_string(ERR_get_error())); + } +} + +ECOpenSSL::~ECOpenSSL() { + EC_GROUP_free(group_); + BN_clear_free(p_); + BN_clear_free(a_); + BN_clear_free(b_); + BN_clear_free(order_); + BN_clear_free(p_minus_one_over_two_); + BN_clear_free(three_); +} + +void ECOpenSSL::add(const Point& in_0, const Point& in_1, Point& out) const { + if (EC_POINT_add(group_, out.data(), in_0.data(), in_1.data(), NULL) == 0) { + throw std::runtime_error("openssl error: " + std::to_string(ERR_get_error())); + } +} + +void ECOpenSSL::invert(const Point& in, Point& out) const { + out = in; + if (EC_POINT_invert(group_, out.data(), NULL) == 0) { + throw std::runtime_error("openssl error: " + std::to_string(ERR_get_error())); + } +} + +void ECOpenSSL::mul(const Point& point, const UInt& scalar, Point& out) const { + if (EC_POINT_mul(group_, out.data(), NULL, point.data(), scalar.data(), NULL) == 0) { + throw std::runtime_error("openssl error: " + std::to_string(ERR_get_error())); + } +} + +void ECOpenSSL::mul_generator(const UInt& scalar, Point& out) const { + if (EC_POINT_mul(group_, out.data(), scalar.data(), NULL, NULL, NULL) == 0) { + throw std::runtime_error("openssl error: " + std::to_string(ERR_get_error())); + } +} + +void ECOpenSSL::create_secret_key(std::shared_ptr prng, SecretKey& out) const { + if (prng == nullptr) { + throw std::invalid_argument("prng is nullptr"); + } + if (out.data() == nullptr) { + throw std::invalid_argument("out is nullptr"); + } + + std::size_t byte_count = static_cast(BN_num_bytes(order_)); + BIGNUM* cap(BN_new()); + if (cap == nullptr) { + throw std::runtime_error("openssl error: " + std::to_string(ERR_get_error())); + } + BN_CTX* ctx(BN_CTX_new()); + if (ctx == nullptr) { + throw std::runtime_error("openssl error: " + std::to_string(ERR_get_error())); + } + std::vector buffer(byte_count, Byte(0xFF)); + BN_bin2bn(reinterpret_cast(buffer.data()), static_cast(byte_count), cap); + if (BN_nnmod(out.data(), cap, order_, ctx) == 0) { + throw std::runtime_error("openssl error: " + std::to_string(ERR_get_error())); + } + BN_sub(cap, cap, out.data()); + do { + do { + prng->generate(byte_count, buffer.data()); + BN_bin2bn(reinterpret_cast(buffer.data()), static_cast(byte_count), out.data()); + } while (BN_cmp(out.data(), cap) != -1); + if (BN_nnmod(out.data(), out.data(), order_, ctx) == 0) { + throw std::runtime_error("openssl error: " + std::to_string(ERR_get_error())); + } + } while (BN_num_bits(out.data()) != BN_num_bits(order_)); + + BN_clear_free(cap); + BN_CTX_free(ctx); +} + +void ECOpenSSL::create_public_key(const SecretKey& key, Point& out) const { + if (key.data() == nullptr) { + throw std::invalid_argument("key is nullptr"); + } + if (out.data() == nullptr) { + throw std::invalid_argument("out is nullptr"); + } + if (EC_POINT_mul(group_, out.data(), key.data(), NULL, NULL, NULL) == 0) { + throw std::runtime_error("openssl error: " + std::to_string(ERR_get_error())); + } +} + +void ECOpenSSL::encrypt(const Point& in, const SecretKey& key, Point& out) const { + if (in.data() == nullptr) { + throw std::invalid_argument("in is nullptr"); + } + if (key.data() == nullptr) { + throw std::invalid_argument("key is nullptr"); + } + if (out.data() == nullptr) { + throw std::invalid_argument("out is nullptr"); + } + + BN_CTX* ctx(BN_CTX_secure_new()); + if (ctx == nullptr) { + throw std::runtime_error("openssl error: " + std::to_string(ERR_get_error())); + } + if (EC_POINT_mul(group_, out.data(), NULL, in.data(), key.data(), ctx) == 0) { + throw std::runtime_error("openssl error: " + std::to_string(ERR_get_error())); + } + BN_CTX_free(ctx); +} + +void ECOpenSSL::decrypt(const Point& in, const SecretKey& key, Point& out) const { + if (in.data() == nullptr) { + throw std::invalid_argument("in is nullptr"); + } + if (key.data() == nullptr) { + throw std::invalid_argument("key is nullptr"); + } + if (out.data() == nullptr) { + throw std::invalid_argument("out is nullptr"); + } + + BN_CTX* ctx(BN_CTX_secure_new()); + if (ctx == nullptr) { + throw std::runtime_error("openssl error: " + std::to_string(ERR_get_error())); + } + BIGNUM* factor(BN_secure_new()); + if (factor == nullptr) { + throw std::runtime_error("openssl error: " + std::to_string(ERR_get_error())); + } + if (BN_mod_inverse(factor, key.data(), order_, ctx) == nullptr) { + throw std::runtime_error("openssl error: " + std::to_string(ERR_get_error())); + } + if (EC_POINT_mul(group_, out.data(), NULL, in.data(), factor, ctx) == 0) { + throw std::runtime_error("openssl error: " + std::to_string(ERR_get_error())); + } + BN_clear_free(factor); + BN_CTX_free(ctx); +} + +void ECOpenSSL::switch_key(const Point& in, const SecretKey& key_old, const SecretKey& key_new, Point& out) const { + if (in.data() == nullptr) { + throw std::invalid_argument("in is nullptr"); + } + if (key_old.data() == nullptr) { + throw std::invalid_argument("key_old is nullptr"); + } + if (key_new.data() == nullptr) { + throw std::invalid_argument("key_new is nullptr"); + } + if (out.data() == nullptr) { + throw std::invalid_argument("out is nullptr"); + } + + BN_CTX* ctx(BN_CTX_secure_new()); + if (ctx == nullptr) { + throw std::runtime_error("openssl error: BN_CTX_secure_new " + std::to_string(ERR_get_error())); + } + BIGNUM* factor(BN_secure_new()); + if (factor == nullptr) { + throw std::runtime_error("openssl error: BN_secure_new " + std::to_string(ERR_get_error())); + } + if (BN_mod_inverse(factor, key_old.data(), order_, ctx) == nullptr) { + throw std::runtime_error("openssl error: BN_mod_inverse " + std::to_string(ERR_get_error())); + } + if (BN_mod_mul(factor, factor, key_new.data(), order_, ctx) == 0) { + throw std::runtime_error("openssl error: BN_mod_mul " + std::to_string(ERR_get_error())); + } + if (EC_POINT_mul(group_, out.data(), NULL, in.data(), factor, ctx) == 0) { + throw std::runtime_error("openssl error: EC_POINT_mul " + std::to_string(ERR_get_error())); + } + BN_clear_free(factor); + BN_CTX_free(ctx); +} + +void ECOpenSSL::hash_to_field( + std::shared_ptr hash, const Byte* in, std::size_t in_byte_count, BIGNUM* out, BN_CTX* ctx) const { + if (in == nullptr) { + throw std::invalid_argument("in is nullptr"); + } + if (out == nullptr) { + throw std::invalid_argument("out is nullptr"); + } + if (ctx == nullptr) { + throw std::invalid_argument("ctx is nullptr"); + } + + std::size_t hash_byte_count = hash->hash_byte_count(); + std::size_t num_iter = (static_cast(BN_num_bytes(p_)) + hash_byte_count - 1) / hash_byte_count + 1; + std::size_t out_byte_count = hash_byte_count * num_iter; + if (num_iter > 255) { + throw std::invalid_argument("in_byte_count is too large"); + } + + std::vector in_buf(in_byte_count + 1); + std::copy_n(in, in_byte_count, in_buf.data() + 1); + std::vector out_buf(out_byte_count); + + for (std::size_t i = 1; i <= num_iter; i++) { + in_buf[0] = static_cast(i); + hash->compute(in_buf.data(), in_buf.size(), out_buf.data() + hash_byte_count * (i - 1), hash_byte_count); + } + if (BN_bin2bn(reinterpret_cast(out_buf.data()), static_cast(out_byte_count), out) == + nullptr) { + throw std::runtime_error("openssl error: " + std::to_string(ERR_get_error())); + } + if (BN_nnmod(out, out, p_, ctx) == 0) { + throw std::runtime_error("openssl error: " + std::to_string(ERR_get_error())); + } +} + +void ECOpenSSL::hash_to_curve(const Byte* in, std::size_t in_byte_count, Point& out) const { + if (in == nullptr) { + throw std::invalid_argument("in is nullptr"); + } + if (out.data() == nullptr) { + throw std::invalid_argument("out is nullptr"); + } + + BN_CTX* ctx(BN_CTX_new()); + if (ctx == nullptr) { + throw std::runtime_error("openssl error: " + std::to_string(ERR_get_error())); + } + BIGNUM* elt(BN_new()); + if (elt == nullptr) { + throw std::runtime_error("openssl error: " + std::to_string(ERR_get_error())); + } + BIGNUM* y_square(BN_new()); + if (y_square == nullptr) { + throw std::runtime_error("openssl error: " + std::to_string(ERR_get_error())); + } + BIGNUM* tmp(BN_new()); + if (tmp == nullptr) { + throw std::runtime_error("openssl error: " + std::to_string(ERR_get_error())); + } + auto hash = hash_factory_->create(); + std::vector in_buf(in_byte_count); + std::copy_n(in, in_byte_count, in_buf.data()); + + bool repeat = true; + + do { + hash_to_field(hash, in_buf.data(), in_buf.size(), elt, ctx); + compute_y_square(elt, y_square, ctx); + if (BN_mod_exp(tmp, y_square, p_minus_one_over_two_, p_, ctx) == 0) { + throw std::runtime_error("openssl error: " + std::to_string(ERR_get_error())); + } + if (BN_is_one(tmp)) { + if (BN_mod_sqrt(tmp, y_square, p_, ctx) == nullptr) { + throw std::runtime_error("openssl error: " + std::to_string(ERR_get_error())); + } + if (BN_is_bit_set(tmp, 0)) { + if (BN_sub(tmp, p_, tmp) == 0) { + throw std::runtime_error("openssl error: " + std::to_string(ERR_get_error())); + } + } + if (EC_POINT_set_affine_coordinates(group_, out.data(), elt, tmp, ctx) == 0) { + throw std::runtime_error("openssl error: " + std::to_string(ERR_get_error())); + } + switch (EC_POINT_is_on_curve(group_, out.data(), ctx)) { + case -1: + throw std::runtime_error("openssl error: " + std::to_string(ERR_get_error())); + case 0: + break; + case 1: + if (EC_POINT_is_at_infinity(group_, out.data()) == 0) { + repeat = false; + } + break; + } + } + in_buf.resize(static_cast(BN_num_bytes(elt))); + BN_bn2bin(elt, reinterpret_cast(in_buf.data())); + } while (repeat); + + BN_clear_free(elt); + BN_clear_free(y_square); + BN_clear_free(tmp); + BN_CTX_free(ctx); +} + +void ECOpenSSL::compute_y_square(const BIGNUM* x, BIGNUM* y_square, BN_CTX* ctx) const { + if (x == nullptr) { + throw std::invalid_argument("x is nullptr"); + } + if (y_square == nullptr) { + throw std::invalid_argument("y_square is nullptr"); + } + if (ctx == nullptr) { + throw std::invalid_argument("ctx is nullptr"); + } + + if (BN_mod_sqr(y_square, x, p_, ctx) == 0) { + throw std::runtime_error("openssl error: " + std::to_string(ERR_get_error())); + } + if (BN_mod_add(y_square, y_square, a_, p_, ctx) == 0) { + throw std::runtime_error("openssl error: " + std::to_string(ERR_get_error())); + } + if (BN_mod_mul(y_square, y_square, x, p_, ctx) == 0) { + throw std::runtime_error("openssl error: " + std::to_string(ERR_get_error())); + } + if (BN_mod_add(y_square, y_square, b_, p_, ctx) == 0) { + throw std::runtime_error("openssl error: " + std::to_string(ERR_get_error())); + } +} + +bool ECOpenSSL::are_equal(const Point& in_0, const Point& in_1) const { + switch (EC_POINT_cmp(group_, in_0.data(), in_1.data(), NULL)) { + case 0: + return true; + case 1: + return false; + case -1: + throw std::runtime_error("openssl error: " + std::to_string(ERR_get_error())); + break; + default: + throw std::runtime_error("bad return from EC_POINT_cmp"); + } + return false; +} + +size_t ECOpenSSL::point_to_bytes(const Point& point, std::size_t out_byte_count, Byte* out) const { + if (point.data() == nullptr) { + throw std::invalid_argument("point is nullptr"); + } + if (out == nullptr) { + return EC_POINT_point2oct(group_, point.data(), POINT_CONVERSION_COMPRESSED, NULL, 0, NULL); + } + std::size_t res = EC_POINT_point2oct(group_, point.data(), POINT_CONVERSION_COMPRESSED, + reinterpret_cast(out), out_byte_count, NULL); + if (res == 0) { + throw std::runtime_error("openssl error: " + std::to_string(ERR_get_error())); + } + return res; +} + +void ECOpenSSL::point_from_bytes(const Byte* in, std::size_t in_byte_count, Point& out) const { + if (in_byte_count == 0) { + throw std::invalid_argument("in_byte_count is zero"); + } + if (in == nullptr && in_byte_count != 0) { + throw std::invalid_argument("in is nullptr"); + } + if (out.data() == nullptr) { + throw std::invalid_argument("out is nullptr"); + } + if (EC_POINT_oct2point(group_, out.data(), reinterpret_cast(in), in_byte_count, NULL) == 0) { + throw std::runtime_error("openssl error: " + std::to_string(ERR_get_error())); + } +} + +} // namespace solo +} // namespace petace diff --git a/src/solo/ec_openssl.h b/src/solo/ec_openssl.h new file mode 100644 index 0000000..bd7d019 --- /dev/null +++ b/src/solo/ec_openssl.h @@ -0,0 +1,340 @@ +// Copyright 2023 TikTok Pte. Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include +#include +#include + +#include "openssl/bn.h" +#include "openssl/ec.h" + +#include "solo/hash.h" +#include "solo/prng.h" +#include "solo/util/defines.h" + +namespace petace { +namespace solo { + +/** + * @brief Provides basic support to Elliptic Curve cryptography using the OpenSSL library. + */ +class ECOpenSSL { +public: + /** + * @brief A SecretKey is an integer. + */ + class SecretKey { + public: + /** + * @brief Creates an empty SecretKey using a secure allocation. + */ + SecretKey() : data_(BN_secure_new()) { + } + + /** + * @brief Securely wipes and deallocates a SecretKey. + */ + ~SecretKey() { + BN_clear_free(data_); + } + + /** + * @brief Returns a pointer to the SecretKey data. + */ + BIGNUM* data() { + return data_; + } + + /** + * @brief Returns a pointer to the SecretKey data. + */ + const BIGNUM* data() const { + return data_; + } + + private: + BIGNUM* data_; + }; + + /** + * @brief A UInt is an unsigned integer. + */ + class UInt { + public: + /** + * @brief Creates an empty UInt. + */ + UInt() : data_(BN_new()) { + } + + /** + * @brief Wipes and deallocates a SecretKey. + */ + ~UInt() { + BN_clear_free(data_); + } + + /** + * @brief Copies a UInt from another. + * + * @param[in] copy The UInt to copy from. + * @throws std::runtime_error if an OpenSSL command fails. + */ + UInt& operator=(const UInt& copy); + + /** + * @brief Returns a pointer to the UInt data. + */ + BIGNUM* data() { + return data_; + } + + /** + * @brief Returns a pointer to the UInt data. + */ + const BIGNUM* data() const { + return data_; + } + + private: + BIGNUM* data_; + }; + + /** + * @brief A Point is a point on a Elliptic Curve. + */ + class Point { + public: + /** + * @brief Creates an empty Point. + * + * @param[in] ec The container of Elliptic Curve parameters. + */ + explicit Point(const ECOpenSSL& ec) : data_(EC_POINT_new(ec.group_)) { + } + + /** + * @brief Creates an empty Point. + * + * @param[in] ec_group The container of Elliptic Curve parameters. + */ + explicit Point(EC_GROUP* ec_group) : data_(EC_POINT_new(ec_group)) { + } + + /** + * @brief Copies a Point from another. + * + * @param[in] copy The Point to copy from. + * @throws std::runtime_error if an OpenSSL command fails. + */ + Point& operator=(const Point& copy); + + /** + * @brief Wipes and deallocates a Point. + */ + ~Point() { + EC_POINT_clear_free(data_); + } + + /** + * @brief Returns a pointer to the Point data. + */ + EC_POINT* data() { + return data_; + } + + /** + * @brief Returns a pointer to the Point data. + */ + const EC_POINT* data() const { + return data_; + } + + private: + EC_POINT* data_; + }; + + /** + * @brief Creates a class of Elliptic Curve operations from a curve ID supported by OpenSSL and a hash algorithm. + * + * @param[in] curve_id A curve ID that defines an Elliptic Curve in OpenSSL. + * @param[in] hash_scheme A hash algorithm that is used in the hash_to_curve operation. + * @throws std::invalid_argument if the algorithm is not supported. + * @throws std::runtime_error if an OpenSSL command fails. + */ + ECOpenSSL(int curve_id, HashScheme hash_scheme); + + ~ECOpenSSL(); + + /** + * @brief Creates a random secret key from a given PRNG. + * + * @param[in] prng The PRNG to generate randomness from. + * @param[out] out The secret key. + * @throws std::invalid_argument if any function parameter is nullptr. + * @throws std::runtime_error if an OpenSSL command fails. + */ + void create_secret_key(std::shared_ptr prng, SecretKey& out) const; + + /** + * @brief Creates a public key from a given secret key. + * + * @param[in] key The secret key to generate a public key from. + * @param[out] out The public key. + * @throws std::invalid_argument if any function parameter is nullptr. + * @throws std::runtime_error if an OpenSSL command fails. + */ + void create_public_key(const SecretKey& key, Point& out) const; + + /** + * @brief Encrypts a plaintext point to a ciphertext point with a given secret key. + * + * @param[in] in The plaintext point. + * @param[in] key The secret key. + * @param[out] out The ciphertext point. + * @throws std::invalid_argument if any function parameter is nullptr. + * @throws std::runtime_error if an OpenSSL command fails. + */ + void encrypt(const Point& in, const SecretKey& key, Point& out) const; + + /** + * @brief Re-encrypts a ciphertext point with a new secret key by decrypting it with the old secret key first. + * + * @param[in] in The ciphertext point decryptable with the old secret key. + * @param[in] key_old The old secret key. + * @param[in] key_new The new secret key. + * @param[out] out The ciphertext point decryptable with the new secret key. + * @throws std::invalid_argument if any function parameter is nullptr. + * @throws std::runtime_error if an OpenSSL command fails. + */ + void switch_key(const Point& in, const SecretKey& key_old, const SecretKey& key_new, Point& out) const; + + /** + * @brief Hash a number of bytes to a point on the defined Elliptic Curve. + * + * @param[in] in The pointer of bytes to be hashed. + * @param[in] in_byte_count The number of bytes to be hashed. + * @param[out] out The hash digest or the point on the Elliptic Curve. + * @throws std::invalid_argument if the pointer of bytes or the output point is nullptr. + * @throws std::runtime_error if an OpenSSL command fails. + */ + void hash_to_curve(const Byte* in, std::size_t in_byte_count, Point& out) const; + + /** + * @brief Decrypts a ciphertext point to a plaintext point with a given secret key. + * + * @param[in] in The ciphertext point. + * @param[in] key The secret key. + * @param[out] out The plaintext point. + * @throws std::invalid_argument if any function parameter is nullptr. + * @throws std::runtime_error if an OpenSSL command fails. + */ + void decrypt(const Point& in, const SecretKey& key, Point& out) const; + + /** + * @brief Returns true if two points are equal; returns false, otherwise. + * + * @param[in] in_0 A point. + * @param[in] in_1 The other point. + * @throws std::runtime_error if an OpenSSL command fails. + */ + bool are_equal(const Point& in_0, const Point& in_1) const; + + /** + * @brief Addition of two points defined by the Elliptic Curve group. + * + * @param[in] in_0 A point. + * @param[in] in_1 The other point. + * @param[out] out The sum of two points. + * @throws std::runtime_error if an OpenSSL command fails. + */ + void add(const Point& in_0, const Point& in_1, Point& out) const; + + /** + * @brief The inverse of a point defined by the Elliptic Curve group. + * + * @param[in] in A point. + * @param[out] out The inverse of the point. + * @throws std::runtime_error if an OpenSSL command fails. + */ + void invert(const Point& in, Point& out) const; + + /** + * @brief Scalar multiplication of a point by a scalar defined by the Elliptic Curve group. + * + * @param[in] point A point. + * @param[in] scalar A scalar. + * @param[out] out The scalar multiplication result. + * @throws std::runtime_error if an OpenSSL command fails. + */ + void mul(const Point& point, const UInt& scalar, Point& out) const; + + /** + * @brief Scalar multiplication of the Elliptic Curve group's generator by a scalar. + * + * @param[in] scalar A scalar. + * @param[out] out The scalar multiplication result. + * @throws std::runtime_error if an OpenSSL command fails. + */ + void mul_generator(const UInt& scalar, Point& out) const; + + /** + * @brief Writes a point into a byte buffer and returns the number of bytes written (if the byte buffer is empty). + * + * @param[in] point The point to be written. + * @param[in] out_byte_count The size of the output byte buffer. + * @param[out] The byte buffer to write to. + * @throws std::invalid_argument if the point is nullptr. + * @throws std::runtime_error if an OpenSSL command fails or if the buffer is not large enough. + */ + std::size_t point_to_bytes(const Point& point, std::size_t out_byte_count, Byte* out = nullptr) const; + + /** + * @brief Reads a point from a byte buffer. + * + * @param[in] in The byte buffer to read from. + * @param[in] in_byte_count The size of the input byte buffer. + * @param[out] The point to write to. + * @throws std::invalid_argument if the input buffer's byte size is zero or either input or output is nullptr. + * @throws std::runtime_error if an OpenSSL command fails or if the buffer is not large enough. + */ + void point_from_bytes(const Byte* in, std::size_t in_byte_count, Point& out) const; + +private: + void hash_to_field( + std::shared_ptr hash, const Byte* in, std::size_t in_byte_count, BIGNUM* out, BN_CTX* ctx) const; + + void compute_y_square(const BIGNUM* x, BIGNUM* y_square, BN_CTX* ctx) const; + + EC_GROUP* group_; + + BIGNUM* p_; + + BIGNUM* a_; + + BIGNUM* b_; + + BIGNUM* order_; + + BIGNUM* p_minus_one_over_two_; + + BIGNUM* three_; + + std::shared_ptr hash_factory_; +}; + +} // namespace solo +} // namespace petace diff --git a/src/solo/hash.cpp b/src/solo/hash.cpp new file mode 100644 index 0000000..4d4ee56 --- /dev/null +++ b/src/solo/hash.cpp @@ -0,0 +1,195 @@ +// Copyright 2023 TikTok Pte. Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "solo/hash.h" + +#include +#include +#include +#include +#include +#include + +#include "openssl/err.h" +#include "openssl/evp.h" + +namespace petace { +namespace solo { + +SOLO_NODISCARD std::shared_ptr Hash::create(HashScheme scheme) { + switch (scheme) { + case HashScheme::SHA_256: + return std::make_shared(); + case HashScheme::SHA3_256: + return std::make_shared(); + case HashScheme::BLAKE2b: + return std::make_shared(); + default: + throw std::invalid_argument("invalid scheme"); + } +} + +SOLO_NODISCARD std::shared_ptr HashFactory::create_factory(HashScheme scheme) { + switch (scheme) { + case HashScheme::SHA_256: + return std::make_shared(); + case HashScheme::SHA3_256: + return std::make_shared(); + case HashScheme::BLAKE2b: + return std::make_shared(); + default: + throw std::invalid_argument("invalid scheme"); + } +} + +HashSHA_256::HashSHA_256() : ctx_(EVP_MD_CTX_new()), md_(EVP_sha256()) { + if (ctx_ == nullptr) { + throw std::invalid_argument("ctx_ is nullptr"); + } +} + +HashSHA_256::~HashSHA_256() { + if (ctx_ != nullptr) { + EVP_MD_CTX_free(ctx_); + } +} + +constexpr std::size_t HashSHA_256::kHashByteCount; + +void HashSHA_256::compute(const Byte* in, std::size_t in_byte_count, Byte* out, std::size_t out_byte_count) { + if (in_byte_count == 0 || out_byte_count == 0) { + return; + } + if (in == nullptr && in_byte_count != 0) { + throw std::invalid_argument("in is nullptr"); + } + if (out == nullptr && out_byte_count != 0) { + throw std::invalid_argument("out is nullptr"); + } + if (out_byte_count > kHashByteCount) { + throw std::invalid_argument("out_byte_count is too large"); + } + if (EVP_DigestInit_ex(ctx_, md_, nullptr) == 0) { + throw std::runtime_error("openssl error: EVP_DigestInit_ex " + std::to_string(ERR_get_error())); + } + if (EVP_DigestUpdate(ctx_, in, in_byte_count) == 0) { + throw std::runtime_error("openssl error: EVP_DigestUpdate " + std::to_string(ERR_get_error())); + } + if (out_byte_count == kHashByteCount) { + if (EVP_DigestFinal_ex(ctx_, reinterpret_cast(out), NULL) == 0) { + throw std::runtime_error("openssl error: EVP_DigestFinal_ex " + std::to_string(ERR_get_error())); + } + } else { + std::array buf_; + if (EVP_DigestFinal_ex(ctx_, reinterpret_cast(buf_.data()), NULL) == 0) { + throw std::runtime_error("openssl error: EVP_DigestFinal_ex " + std::to_string(ERR_get_error())); + } + std::copy_n(buf_.data(), out_byte_count, out); + } +} + +HashSHA3_256::HashSHA3_256() : ctx_(EVP_MD_CTX_new()), md_(EVP_sha3_256()) { + if (ctx_ == nullptr) { + throw std::invalid_argument("ctx_ is nullptr"); + } +} + +HashSHA3_256::~HashSHA3_256() { + if (ctx_ != nullptr) { + EVP_MD_CTX_free(ctx_); + } +} + +constexpr std::size_t HashSHA3_256::kHashByteCount; + +void HashSHA3_256::compute(const Byte* in, std::size_t in_byte_count, Byte* out, std::size_t out_byte_count) { + if (in_byte_count == 0 || out_byte_count == 0) { + return; + } + if (in == nullptr && in_byte_count != 0) { + throw std::invalid_argument("in is nullptr"); + } + if (out == nullptr && out_byte_count != 0) { + throw std::invalid_argument("out is nullptr"); + } + if (out_byte_count > kHashByteCount) { + throw std::invalid_argument("out_byte_count is too large"); + } + if (EVP_DigestInit_ex(ctx_, md_, nullptr) == 0) { + throw std::runtime_error("openssl error: EVP_DigestInit_ex " + std::to_string(ERR_get_error())); + } + if (EVP_DigestUpdate(ctx_, in, in_byte_count) == 0) { + throw std::runtime_error("openssl error: EVP_DigestUpdate " + std::to_string(ERR_get_error())); + } + if (out_byte_count == kHashByteCount) { + if (EVP_DigestFinal_ex(ctx_, reinterpret_cast(out), NULL) == 0) { + throw std::runtime_error("openssl error: EVP_DigestFinal_ex " + std::to_string(ERR_get_error())); + } + } else { + std::array buf_; + if (EVP_DigestFinal_ex(ctx_, reinterpret_cast(buf_.data()), NULL) == 0) { + throw std::runtime_error("openssl error: EVP_DigestFinal_ex " + std::to_string(ERR_get_error())); + } + std::copy_n(buf_.data(), out_byte_count, out); + } +} + +HashBLAKE2b::HashBLAKE2b() : ctx_(EVP_MD_CTX_new()), md_(EVP_blake2b512()) { + if (ctx_ == nullptr) { + throw std::invalid_argument("ctx_ is nullptr"); + } +} + +HashBLAKE2b::~HashBLAKE2b() { + if (ctx_ != nullptr) { + EVP_MD_CTX_free(ctx_); + } +} + +constexpr std::size_t HashBLAKE2b::kHashByteCount; + +void HashBLAKE2b::compute(const Byte* in, std::size_t in_byte_count, Byte* out, std::size_t out_byte_count) { + if (in_byte_count == 0 || out_byte_count == 0) { + return; + } + if (in == nullptr && in_byte_count != 0) { + throw std::invalid_argument("in is nullptr"); + } + if (out == nullptr && out_byte_count != 0) { + throw std::invalid_argument("out is nullptr"); + } + if (out_byte_count > kHashByteCount) { + throw std::invalid_argument("out_byte_count is too large"); + } + if (EVP_DigestInit_ex(ctx_, md_, nullptr) == 0) { + throw std::runtime_error("openssl error: EVP_DigestInit_ex " + std::to_string(ERR_get_error())); + } + if (EVP_DigestUpdate(ctx_, in, in_byte_count) == 0) { + throw std::runtime_error("openssl error: EVP_DigestUpdate " + std::to_string(ERR_get_error())); + } + if (out_byte_count == kHashByteCount) { + if (EVP_DigestFinal_ex(ctx_, reinterpret_cast(out), NULL) == 0) { + throw std::runtime_error("openssl error: EVP_DigestFinal_ex " + std::to_string(ERR_get_error())); + } + } else { + std::array buf_; + if (EVP_DigestFinal_ex(ctx_, reinterpret_cast(buf_.data()), NULL) == 0) { + throw std::runtime_error("openssl error: EVP_DigestFinal_ex " + std::to_string(ERR_get_error())); + } + std::copy_n(buf_.data(), out_byte_count, out); + } +} + +} // namespace solo +} // namespace petace diff --git a/src/solo/hash.h b/src/solo/hash.h new file mode 100644 index 0000000..3f66601 --- /dev/null +++ b/src/solo/hash.h @@ -0,0 +1,277 @@ +// Copyright 2023 TikTok Pte. Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include +#include +#include +#include + +#include "openssl/evp.h" + +#include "solo/util/defines.h" + +namespace petace { +namespace solo { + +/** + * @brief Describes the hash algorithm to use. + */ +enum class HashScheme : std::uint8_t { SHA_256 = 0, SHA3_256 = 3, BLAKE2b = 6 }; + +/** + * @brief Provides the base class for a hash function. + */ +class Hash { +public: + Hash() = default; + + virtual ~Hash() = default; + + /** + * @brief Create a shared pointer of a hash function from the algorithm chosen. + * + * @param[in] scheme A hash algorithm. + * @throws std::invalid_argument if the algorithm is not supported. + */ + SOLO_NODISCARD static std::shared_ptr create(HashScheme scheme); + + virtual std::size_t hash_byte_count() const = 0; + + virtual void compute(const Byte* in, std::size_t in_byte_count, Byte* out, std::size_t out_byte_count) = 0; + + /** + * @brief Compute the hash of an input byte array without truncating the hash. + * + * @param[in] in The pointer of the input byte array. + * @param[in] in_byte_count The number of bytes in the input byte array. + * @param[out] out The pointer of the output byte array. + * @throws std::invalid_argumment if either in or out is nullptr with non-zero byte count. + */ + void compute(const Byte* in, std::size_t in_byte_count, Byte* out) { + compute(in, in_byte_count, out, hash_byte_count()); + } +}; + +/** + * @brief Provides the base class for a hash function factory. + */ +class HashFactory { +public: + virtual ~HashFactory() = default; + + /** + * @brief Creates a shared pointer of a hash function factory from the algorithm chosen. + * + * A hash function factory is designed to provide keyed hash functions in a future release. + * + * @param[in] scheme A hash algorithm. + * @throws std::invalid_argument if the algorithm is not supported. + */ + SOLO_NODISCARD static std::shared_ptr create_factory(HashScheme scheme); + + /** + * @brief Creates a shared pointer of a hash function from the algorithm chosen. + * + * @param[in] scheme A hash algorithm. + * @throws std::invalid_argument if the algorithm is not supported. + * @throws std::runtime_error if an OpenSSL command fails. + */ + SOLO_NODISCARD std::shared_ptr create(HashScheme scheme) { + return Hash::create(scheme); + } + + SOLO_NODISCARD virtual std::shared_ptr create() = 0; +}; + +/** + * @brief Implements a SHA-256 hash function. + */ +class HashSHA_256 : public Hash { +public: + HashSHA_256(); + + ~HashSHA_256(); + + /** + * @brief The default number of bytes in a SHA-256 hash. + */ + static constexpr std::size_t kHashByteCount = 32; + + /** + * @brief Creates a shared pointer of a SHA-256 hash function. + */ + SOLO_NODISCARD static std::shared_ptr create() { + return std::make_shared(); + } + + /** + * @brief Returns the default number of bytes in a SHA-256 hash. + */ + std::size_t hash_byte_count() const override { + return kHashByteCount; + } + + /** + * @brief Compute the SHA-256 hash of an input byte array and truncate output to out_byte_count bytes. + * + * @param[in] in The pointer of the input byte array. + * @param[in] in_byte_count The number of bytes in the input byte array. + * @param[out] out The pointer of the output byte array. + * @param[in] out_byte_count The number of bytes expected in the out byte array. + * @throws std::invalid_argumment if either in or out is nullptr with non-zero byte count or if out_byte_count is + * larger than kHashByteCount. + * @throws std::runtime_error if an OpenSSL command fails. + */ + void compute(const Byte* in, std::size_t in_byte_count, Byte* out, std::size_t out_byte_count) override; + +private: + EVP_MD_CTX* ctx_; + const EVP_MD* md_; +}; + +/** + * @brief Implements a SHA-256 hash function factory. + */ +class HashFactorySHA_256 : public HashFactory { +public: + /** + * @brief Creates a shared pointer of a SHA-256 hash function. + */ + SOLO_NODISCARD std::shared_ptr create() override { + return HashSHA_256::create(); + } +}; + +/** + * @brief Implements a SHA3-256 hash function. + */ +class HashSHA3_256 : public Hash { +public: + HashSHA3_256(); + + ~HashSHA3_256(); + + /** + * @brief The default number of bytes in a SHA3-256 hash. + */ + static constexpr std::size_t kHashByteCount = 32; + + /** + * @brief Creates a shared pointer of a SHA3-256 hash function. + */ + SOLO_NODISCARD static std::shared_ptr create() { + return std::make_shared(); + } + + /** + * @brief Returns the default number of bytes in a SHA3-256 hash. + */ + std::size_t hash_byte_count() const override { + return kHashByteCount; + } + + /** + * @brief Compute the SHA3-256 hash of an input byte array and truncate output to out_byte_count bytes. + * + * @param[in] in The pointer of the input byte array. + * @param[in] in_byte_count The number of bytes in the input byte array. + * @param[out] out The pointer of the output byte array. + * @param[in] out_byte_count The number of bytes expected in the out byte array. + * @throws std::invalid_argumment if either in or out is nullptr with non-zero byte count or if out_byte_count is + * larger than kHashByteCount. + * @throws std::runtime_error if an OpenSSL command fails. + */ + void compute(const Byte* in, std::size_t in_byte_count, Byte* out, std::size_t out_byte_count) override; + +private: + EVP_MD_CTX* ctx_; + const EVP_MD* md_; +}; + +/** + * @brief Implements a SHA3-256 hash function factory. + */ +class HashFactorySHA3_256 : public HashFactory { +public: + /** + * @brief Creates a shared pointer of a SHA3-256 hash function. + */ + SOLO_NODISCARD std::shared_ptr create() override { + return HashSHA3_256::create(); + } +}; + +/** + * @brief Implements a BLAKE2b hash function. + */ +class HashBLAKE2b : public Hash { +public: + HashBLAKE2b(); + + ~HashBLAKE2b(); + + /** + * @brief The default number of bytes in a BLAKE2b hash. + */ + static constexpr std::size_t kHashByteCount = 64; + + /** + * @brief Creates a shared pointer of a BLAKE2b hash function. + */ + SOLO_NODISCARD static std::shared_ptr create() { + return std::make_shared(); + } + + /** + * @brief Returns the default number of bytes in a BLAKE2b hash. + */ + std::size_t hash_byte_count() const override { + return kHashByteCount; + } + + /** + * @brief Compute the BLAKE2b hash of an input byte array and truncate output to out_byte_count bytes. + * + * @param[in] in The pointer of the input byte array. + * @param[in] in_byte_count The number of bytes in the input byte array. + * @param[out] out The pointer of the output byte array. + * @param[in] out_byte_count The number of bytes expected in the out byte array. + * @throws std::invalid_argumment if either in or out is nullptr with non-zero byte count or if out_byte_count is + * larger than kHashByteCount. + * @throws std::runtime_error if an OpenSSL command fails. + */ + void compute(const Byte* in, std::size_t in_byte_count, Byte* out, std::size_t out_byte_count) override; + +private: + EVP_MD_CTX* ctx_; + const EVP_MD* md_; +}; + +/** + * @brief Implements a BLAKE2b hash function factory. + */ +class HashFactoryBLAKE2b : public HashFactory { +public: + /** + * @brief Creates a shared pointer of a BLAKE2b hash function. + */ + SOLO_NODISCARD std::shared_ptr create() override { + return HashBLAKE2b::create(); + } +}; + +} // namespace solo +} // namespace petace diff --git a/src/solo/prng.cpp b/src/solo/prng.cpp new file mode 100644 index 0000000..cd40e94 --- /dev/null +++ b/src/solo/prng.cpp @@ -0,0 +1,220 @@ +// Copyright 2023 TikTok Pte. Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "solo/util/config.h" +#ifdef SOLO_USE_AES_INTRIN +#include "solo/util/aes_ecb_ctr.h" +#endif +#include +#include +#include +#include +#include +#include +#include + +#include "openssl/err.h" +#include "openssl/evp.h" + +#include "solo/prng.h" +#include "solo/util/blake2.h" +#include "solo/util/defines.h" + +namespace petace { +namespace solo { + +PRNG::PRNG(std::size_t seed_byte_count, std::size_t buffer_byte_count) { + if (seed_byte_count == 0) { + throw std::invalid_argument("seed_byte_count is zero"); + } + if (buffer_byte_count == 0) { + throw std::invalid_argument("buffer_byte_count is zero"); + } + seed_.resize(seed_byte_count); + get_random_byte_array(seed_.size(), seed_.data()); + buffer_.resize(buffer_byte_count); + buffer_head_ = buffer_.end(); +} + +PRNG::PRNG(const std::vector& seed, std::size_t buffer_byte_count) { + if (seed.size() == 0) { + throw std::invalid_argument("seed is empty"); + } + if (buffer_byte_count == 0) { + throw std::invalid_argument("buffer_byte_count is zero"); + } + seed_.resize(seed.size()); + std::copy_n(seed.cbegin(), seed.size(), seed_.begin()); + buffer_.resize(buffer_byte_count); + buffer_head_ = buffer_.end(); +} + +void PRNG::generate(std::size_t out_byte_count, Byte* out) { + if (out == nullptr && out_byte_count != 0) { + throw std::invalid_argument("out is nullptr"); + } + while (out_byte_count) { + std::size_t current_byte_count = + std::min(out_byte_count, static_cast(distance(buffer_head_, buffer_.end()))); + std::copy_n(buffer_head_, current_byte_count, out); + buffer_head_ += static_cast(current_byte_count); + out += current_byte_count; + out_byte_count -= current_byte_count; + + if (buffer_head_ == buffer_.end()) { + refill_buffer(); + buffer_head_ = buffer_.begin(); + } + } +} + +void PRNG::get_random_byte_array(std::size_t out_byte_count, Byte* out) { + if (out_byte_count == 0) { + return; + } + if (out_byte_count != 0 && out == nullptr) { + throw std::invalid_argument("out is nullptr"); + } + std::random_device rd("/dev/urandom"); + while (out_byte_count >= 4) { + *reinterpret_cast(out) = rd(); + out += 4; + out_byte_count -= 4; + } + if (out_byte_count) { + std::uint32_t last = rd(); + memcpy(out, &last, out_byte_count); + } +} + +template <> +void PRNGImpl::refill_buffer() { + EVP_MD_CTX* ctx(EVP_MD_CTX_new()); + std::vector seed_ext(seed_.size() + sizeof(counter_)); + auto seed_ext_tail = std::copy_n(seed_.cbegin(), seed_.size(), seed_ext.begin()); + std::copy_n(reinterpret_cast(&counter_), sizeof(counter_), seed_ext_tail); + if (EVP_DigestInit_ex(ctx, EVP_shake128(), nullptr) == 0) { + throw std::runtime_error("openssl error: EVP_DigestInit_ex " + std::to_string(ERR_get_error())); + } + if (EVP_DigestUpdate(ctx, seed_ext.data(), seed_ext.size()) == 0) { + throw std::runtime_error("openssl error: EVP_DigestUpdate " + std::to_string(ERR_get_error())); + } + if (EVP_DigestFinalXOF(ctx, reinterpret_cast(buffer_.data()), buffer_.size()) == 0) { + throw std::runtime_error("openssl error: EVP_DigestFinalXOF " + std::to_string(ERR_get_error())); + } + EVP_MD_CTX_free(ctx); + counter_++; +} + +template <> +void PRNGImpl::refill_buffer() { + if (blake2xb(buffer_.data(), buffer_.size(), &counter_, sizeof(counter_), seed_.data(), + seed_.size() * sizeof(Byte)) != 0) { + throw std::runtime_error("blake2xb failed"); + } + counter_++; +} + +#ifdef SOLO_USE_AES_INTRIN +template <> +void PRNGImpl::refill_buffer() { + util::aes::Block aes_key; + std::copy_n(seed_.data(), sizeof(aes_key), reinterpret_cast(&aes_key)); + util::aes::RoundKey aes_round_key; + util::aes::set_round_key(aes_key, aes_round_key); + util::aes::encrypt_ecb_ctr(aes_round_key, counter_, reinterpret_cast(buffer_.data()), + buffer_.size() / sizeof(util::aes::Block)); + counter_ += buffer_.size() / sizeof(util::aes::Block); +} +#endif + +PRNGFactory::PRNGFactory(petace::solo::PRNGScheme scheme, std::size_t seed_byte_count) + : scheme_(scheme), seed_byte_count_(seed_byte_count) { + if (seed_byte_count == 0) { + throw std::invalid_argument("seed_byte_count is zero"); + } + if (scheme != PRNGScheme::SHAKE_128 && scheme != PRNGScheme::BLAKE2Xb && scheme != PRNGScheme::AES_ECB_CTR) { + throw std::invalid_argument("unsupported PRNGScheme"); + } +#ifdef SOLO_USE_AES_INTRIN + if (scheme == PRNGScheme::AES_ECB_CTR && seed_byte_count != 16) { + throw std::invalid_argument("PRNGScheme::AES_ECB_CTR requires a 128-bit seed"); + } +#endif +} + +std::shared_ptr petace::solo::PRNGFactory::create(std::size_t buffer_byte_count) { +#ifdef SOLO_DEBUG + if (use_fixed_seed_) { + switch (scheme_) { + case PRNGScheme::SHAKE_128: + return std::make_shared>(fixed_seed_, buffer_byte_count); + case PRNGScheme::BLAKE2Xb: + return std::make_shared>(fixed_seed_, buffer_byte_count); +#ifdef SOLO_USE_AES_INTRIN + case PRNGScheme::AES_ECB_CTR: + return std::make_shared>(fixed_seed_, buffer_byte_count); +#endif + } + } +#endif + switch (scheme_) { + case PRNGScheme::SHAKE_128: + return std::make_shared>(seed_byte_count_, buffer_byte_count); + case PRNGScheme::BLAKE2Xb: + return std::make_shared>(seed_byte_count_, buffer_byte_count); +#ifdef SOLO_USE_AES_INTRIN + case PRNGScheme::AES_ECB_CTR: + return std::make_shared>(seed_byte_count_, buffer_byte_count); +#endif + } + return nullptr; +} + +std::shared_ptr petace::solo::PRNGFactory::create(const std::vector& seed, std::size_t buffer_byte_count) { + if (seed.size() != seed_byte_count_) { + throw std::invalid_argument("seed.size() does not match seed_byte_count_"); + } + switch (scheme_) { + case PRNGScheme::SHAKE_128: + return std::make_shared>(seed, buffer_byte_count); + case PRNGScheme::BLAKE2Xb: + return std::make_shared>(seed, buffer_byte_count); +#ifdef SOLO_USE_AES_INTRIN + case PRNGScheme::AES_ECB_CTR: + return std::make_shared>(seed, buffer_byte_count); +#endif + } + return nullptr; +} + +#ifdef SOLO_DEBUG +PRNGFactory::PRNGFactory(petace::solo::PRNGScheme scheme, const std::vector& seed) : scheme_(scheme) { + if (seed.size() == 0) { + throw std::invalid_argument("seed is empty"); + } +#ifdef SOLO_USE_AES_INTRIN + if (scheme == PRNGScheme::AES_ECB_CTR && seed.size() != 16) { + throw std::invalid_argument("PRNGScheme::AES_ECB_CTR requires a 128-bit seed"); + } +#endif + seed_byte_count_ = seed.size(); + use_fixed_seed_ = true; + fixed_seed_.resize(seed_byte_count_); + std::copy_n(seed.cbegin(), seed_byte_count_, fixed_seed_.begin()); +} +#endif + +} // namespace solo +} // namespace petace diff --git a/src/solo/prng.h b/src/solo/prng.h new file mode 100644 index 0000000..94938ec --- /dev/null +++ b/src/solo/prng.h @@ -0,0 +1,233 @@ +// Copyright 2023 TikTok Pte. Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include "solo/util/config.h" +#ifdef SOLO_USE_AES_INTRIN +#include "solo/util/aes_ecb_ctr.h" +#endif +#include +#include +#include +#include +#include +#include +#include + +#include "solo/util/defines.h" + +namespace petace { +namespace solo { + +/** + * @brief Describes the pseudorandom number generator algorithm to use. + */ +enum class PRNGScheme : std::uint8_t { + SHAKE_128 = 0, + BLAKE2Xb = 1, +#ifdef SOLO_USE_AES_INTRIN + AES_ECB_CTR = 2 +#endif +}; + +/** + * @brief Provides the base class for a pseudorandom number generator. + */ +class PRNG { +public: + PRNG() = delete; + + /** + * @brief Constructs a pseudorandom number generator and set the byte size of a seed and refill buffer. + * + * @param[in] seed_byte_count The byte size of a seed. + * @param[in] buffer_byte_count The byte size of the refill buffer. + * @throws std::invalid_argument if either input is zero. + */ + explicit PRNG(std::size_t seed_byte_count = 16, std::size_t buffer_byte_count = 512); + + /** + * @brief Constructs a pseudorandom number generator and set a seed and the byte size of refill buffer. + * + * @param[in] seed The seed for the random number generator. + * @param[in] buffer_byte_count The byte size of the refill buffer. + * @throws std::invalid_argument if the byte size of the refill buffer is zero or if the seed is empty. + */ + explicit PRNG(const std::vector& seed, std::size_t buffer_byte_count = 512); + + virtual ~PRNG() = default; + + /** + * @brief Generate a requested number of bytes of randomness to a given buffer. + * + * @param[in] out_byte_count The number of bytes of randomness requested. + * @param[out] out The buffer that stores the generated randomness. + * @throws std::invalid_argument if non-zero number of bytes is requested and the buffer is nullptr. + */ + void generate(std::size_t out_byte_count, Byte* out); + + /** + * @brief Returns the byte size of the seed. + */ + SOLO_NODISCARD std::size_t seed_byte_count() const noexcept { + return seed_.size(); + } + + /** + * @brief Returns the seed. + */ + SOLO_NODISCARD const std::vector& seed() const noexcept { + return seed_; + } + + /** + * @brief Obtain a given number of bytes of randomness from /dev/urandom and store them in a given buffer. + * + * @param[in] out_byte_count The number of bytes of randomness requested. + * @param[out] out The buffer that stores the generated randomness. + * @throws std::invalid_argument if non-zero number of bytes is requested and the buffer is nullptr. + */ + static void get_random_byte_array(std::size_t out_byte_count, Byte* out); + +protected: + virtual void refill_buffer() = 0; + + SOLO_NODISCARD std::size_t buffer_byte_count() noexcept { + return buffer_.size(); + } + + std::vector seed_{}; + + std::vector buffer_{}; + + std::vector::iterator buffer_head_; + + std::uint64_t counter_ = 0; +}; + +/** + * @brief Wraps a PRNG object as a randomness source in C++ standard random number generators. + */ +class PRNGStandard { +public: + using result_type = std::uint64_t; + + /** + Creates a new PRNGStandard from a PRNG. + + @param[in] prng A PRNG. + @throws std::invalid_argument if prng is nullptr. + */ + PRNGStandard(std::shared_ptr prng) : prng_(prng) { + if (prng_ == nullptr) { + throw std::invalid_argument("prng is nullptr"); + } + } + + /** + @brief Returns the smallest possible output value. + */ + SOLO_NODISCARD inline static constexpr result_type min() noexcept { + return std::numeric_limits::min(); + } + + /** + @brief Returns the largest possible output value. + */ + SOLO_NODISCARD static constexpr result_type max() noexcept { + return std::numeric_limits::max(); + } + + /** + @brief Returns a new random number. + */ + SOLO_NODISCARD result_type operator()() { + result_type out; + prng_->generate(sizeof(out), reinterpret_cast(&out)); + return out; + } + +private: + std::shared_ptr prng_; +}; + +template +class PRNGImpl : public PRNG { +public: + explicit PRNGImpl(std::size_t seed_byte_count = 16, std::size_t buffer_byte_count = 512) + : PRNG(seed_byte_count, buffer_byte_count) { + } + + explicit PRNGImpl(const std::vector& seed, std::size_t buffer_byte_count = 512) + : PRNG(seed, buffer_byte_count) { + } + + ~PRNGImpl() = default; + +protected: + void refill_buffer() override; +}; + +/** + * @brief Implements a PRNG factory. + */ +class PRNGFactory { +public: + /** + * @brief Creates a PRNG factory from the algorithm chosen and a seed's byte size. + * + * @param[in] scheme A PRNG algorithm. + * @param[in] seed_byte_count The byte size of a seed. + * @throws std::invalid_argument if the byte size of a seed is zero or if the algorithm is not supported. + * @throws std::invalid_argument if AES_ECB_CTR is chosen and the byte size of a seed is not 16. + */ + explicit PRNGFactory(PRNGScheme scheme, std::size_t seed_byte_count = 16); + + /** + * @brief Creates a shared pointer of a PRNG from a refill buffer's byte size. + * + * @param[in] buffer_byte_count The byte size of a refill buffer. + * @throws std::invalid_argument if the refill buffer's byte size is zero. + */ + SOLO_NODISCARD std::shared_ptr create(std::size_t buffer_byte_count = 512); + + /** + * @brief Creates a shared pointer of a PRNG from a given seed and a refill buffer's byte size. + * + * @param[in] seed The seed of PRNG. + * @param[in] buffer_byte_count The byte size of a refill buffer. + * @throws std::invalid_argument if the given seed's byte size does not match the factory's seed's byte size or if + * the refill buffer's byte size is zero. + */ + SOLO_NODISCARD std::shared_ptr create(const std::vector& seed, std::size_t buffer_byte_count = 512); + +private: + PRNGScheme scheme_; + + std::size_t seed_byte_count_ = 0; + +#ifdef SOLO_DEBUG +public: + PRNGFactory(PRNGScheme scheme, const std::vector& seed); + +private: + bool use_fixed_seed_ = false; + + std::vector fixed_seed_{}; +#endif +}; + +} // namespace solo +} // namespace petace diff --git a/src/solo/sampling.cpp b/src/solo/sampling.cpp new file mode 100644 index 0000000..41683c6 --- /dev/null +++ b/src/solo/sampling.cpp @@ -0,0 +1,68 @@ +// Copyright 2023 TikTok Pte. Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either expouts or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "solo/sampling.h" + +#include +#include +#include +#include + +#include "solo/prng.h" +#include "solo/util/defines.h" + +namespace petace { +namespace solo { + +SOLO_NODISCARD Byte sample_uniform_byte(petace::solo::PRNG& prng) noexcept { + Byte out; + prng.generate(sizeof(out), &out); + return out; +} + +SOLO_NODISCARD std::uint32_t sample_uniform_uint32(petace::solo::PRNG& prng) noexcept { + std::uint32_t out; + prng.generate(sizeof(out), reinterpret_cast(&out)); + return out; +} + +SOLO_NODISCARD std::uint64_t sample_uniform_uint64(petace::solo::PRNG& prng) noexcept { + std::uint64_t out; + prng.generate(sizeof(out), reinterpret_cast(&out)); + return out; +} + +void sample_uniform_byte_array(petace::solo::PRNG& prng, std::size_t byte_count, Byte* out) { + if (out == nullptr) { + throw std::invalid_argument("out is nullptr"); + } + prng.generate(byte_count, out); +} + +void sample_uniform_uint32_array(petace::solo::PRNG& prng, std::size_t uint32_count, std::uint32_t* out) { + if (out == nullptr) { + throw std::invalid_argument("out is nullptr"); + } + prng.generate(uint32_count * sizeof(std::uint32_t), reinterpret_cast(out)); +} + +void sample_uniform_uint64_array(petace::solo::PRNG& prng, std::size_t uint64_count, std::uint64_t* out) { + if (out == nullptr) { + throw std::invalid_argument("out is nullptr"); + } + prng.generate(uint64_count * sizeof(std::uint64_t), reinterpret_cast(out)); +} + +} // namespace solo +} // namespace petace diff --git a/src/solo/sampling.h b/src/solo/sampling.h new file mode 100644 index 0000000..5508f24 --- /dev/null +++ b/src/solo/sampling.h @@ -0,0 +1,80 @@ +// Copyright 2023 TikTok Pte. Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include +#include +#include +#include + +#include "solo/prng.h" +#include "solo/util/defines.h" + +namespace petace { +namespace solo { + +/** + * @brief Returns a random byte generated by a given PRNG. + * + * @param[in] prng A PRNG to generate randomness from. + */ +SOLO_NODISCARD Byte sample_uniform_byte(PRNG& prng) noexcept; + +/** + * @brief Returns a random 32-bit unsigned integer generated by a given PRNG. + * + * @param[in] prng A PRNG to generate randomness from. + */ +SOLO_NODISCARD std::uint32_t sample_uniform_uint32(PRNG& prng) noexcept; + +/** + * @brief Returns a random 64-bit unsigned integer generated by a given PRNG. + * + * @param[in] prng A PRNG to generate randomness from. + */ +SOLO_NODISCARD std::uint64_t sample_uniform_uint64(PRNG& prng) noexcept; + +/** + * @brief Generates a given number of random bytes from a given PRNG to a buffer. + * + * @param[in] prng A PRNG to generate randomness from. + * @param[in] byte_count The number of random bytes requested. + * @param[out] out The buffer that stores the generated randomness. + * @throws std::invalid_argument if the output buffer is nullptr. + */ +void sample_uniform_byte_array(PRNG& prng, std::size_t byte_count, Byte* out); + +/** + * @brief Generates a given number of random 32-bit unsigned integers from a given PRNG to a buffer. + * + * @param[in] prng A PRNG to generate randomness from. + * @param[in] uint32_count The number of random 32-bit unsigned integers requested. + * @param[out] out The buffer that stores the generated randomness. + * @throws std::invalid_argument if the output buffer is nullptr. + */ +void sample_uniform_uint32_array(PRNG& prng, std::size_t uint32_count, std::uint32_t* out); + +/** + * @brief Generates a given number of random 64-bit unsigned integers from a given PRNG to a buffer. + * + * @param[in] prng A PRNG to generate randomness from. + * @param[in] uint64_count The number of random 64-bit unsigned integers requested. + * @param[out] out The buffer that stores the generated randomness. + * @throws std::invalid_argument if the output buffer is nullptr. + */ +void sample_uniform_uint64_array(PRNG& prng, std::size_t uint64_count, std::uint64_t* out); + +} // namespace solo +} // namespace petace diff --git a/src/solo/solo.h b/src/solo/solo.h new file mode 100644 index 0000000..8cadded --- /dev/null +++ b/src/solo/solo.h @@ -0,0 +1,20 @@ +// Copyright 2023 TikTok Pte. Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include "solo/ec_openssl.h" +#include "solo/hash.h" +#include "solo/prng.h" +#include "solo/sampling.h" diff --git a/src/solo/util/CMakeLists.txt b/src/solo/util/CMakeLists.txt new file mode 100644 index 0000000..8dd362d --- /dev/null +++ b/src/solo/util/CMakeLists.txt @@ -0,0 +1,43 @@ +# Copyright 2023 TikTok Pte. Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Source files in this directory +set(SOLO_SOURCE_FILES ${SOLO_SOURCE_FILES} + ${CMAKE_CURRENT_LIST_DIR}/blake2b-ref.c + ${CMAKE_CURRENT_LIST_DIR}/blake2xb-ref.c +) + +# Add header files for installation +install( + FILES + ${CMAKE_CURRENT_LIST_DIR}/blake2.h + ${CMAKE_CURRENT_LIST_DIR}/blake2-impl.h + ${CMAKE_CURRENT_LIST_DIR}/defines.h + DESTINATION + ${SOLO_INCLUDES_INSTALL_DIR}/solo/util +) + +if (SOLO_USE_AES_INTRIN) + set(SOLO_SOURCE_FILES ${SOLO_SOURCE_FILES} + ${CMAKE_CURRENT_LIST_DIR}/aes_ecb_ctr.cpp + ) + install( + FILES + ${CMAKE_CURRENT_LIST_DIR}/aes_ecb_ctr.h + DESTINATION + ${SOLO_INCLUDES_INSTALL_DIR}/solo/util + ) +endif() + +set(SOLO_SOURCE_FILES ${SOLO_SOURCE_FILES} PARENT_SCOPE) diff --git a/src/solo/util/aes_ecb_ctr.cpp b/src/solo/util/aes_ecb_ctr.cpp new file mode 100644 index 0000000..8bb737a --- /dev/null +++ b/src/solo/util/aes_ecb_ctr.cpp @@ -0,0 +1,197 @@ +// Copyright 2023 TikTok Pte. Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "solo/util/config.h" +#ifdef SOLO_USE_AES_INTRIN + +#include + +#include +#include +#include +#include +#include +#include + +#include "solo/util/aes_ecb_ctr.h" + +namespace petace { +namespace solo { +namespace util { +namespace aes { +Block key_gen_helper(Block key, Block key_rcon) { + key_rcon = _mm_shuffle_epi32(key_rcon, _MM_SHUFFLE(3, 3, 3, 3)); + key = _mm_xor_si128(key, _mm_slli_si128(key, 4)); + key = _mm_xor_si128(key, _mm_slli_si128(key, 4)); + key = _mm_xor_si128(key, _mm_slli_si128(key, 4)); + return _mm_xor_si128(key, key_rcon); +} + +void set_round_key(const Block& key, RoundKey& round_key) { + round_key[0] = key; + round_key[1] = key_gen_helper(round_key[0], _mm_aeskeygenassist_si128(round_key[0], 0x01)); + round_key[2] = key_gen_helper(round_key[1], _mm_aeskeygenassist_si128(round_key[1], 0x02)); + round_key[3] = key_gen_helper(round_key[2], _mm_aeskeygenassist_si128(round_key[2], 0x04)); + round_key[4] = key_gen_helper(round_key[3], _mm_aeskeygenassist_si128(round_key[3], 0x08)); + round_key[5] = key_gen_helper(round_key[4], _mm_aeskeygenassist_si128(round_key[4], 0x10)); + round_key[6] = key_gen_helper(round_key[5], _mm_aeskeygenassist_si128(round_key[5], 0x20)); + round_key[7] = key_gen_helper(round_key[6], _mm_aeskeygenassist_si128(round_key[6], 0x40)); + round_key[8] = key_gen_helper(round_key[7], _mm_aeskeygenassist_si128(round_key[7], 0x80)); + round_key[9] = key_gen_helper(round_key[8], _mm_aeskeygenassist_si128(round_key[8], 0x1B)); + round_key[10] = key_gen_helper(round_key[9], _mm_aeskeygenassist_si128(round_key[9], 0x36)); +} + +void encrypt_ecb_ctr(const RoundKey& round_key, std::uint64_t counter, Block* out, std::size_t out_block_count) { + Block counter_block = _mm_set_epi64x(0, static_cast(counter)); + + const std::size_t step = 8; + std::size_t length = out_block_count - out_block_count % step; + const Block b0 = _mm_set_epi64x(0, 0); + const Block b1 = _mm_set_epi64x(0, 1ull); + const Block b2 = _mm_set_epi64x(0, 2ull); + const Block b3 = _mm_set_epi64x(0, 3ull); + const Block b4 = _mm_set_epi64x(0, 4ull); + const Block b5 = _mm_set_epi64x(0, 5ull); + const Block b6 = _mm_set_epi64x(0, 6ull); + const Block b7 = _mm_set_epi64x(0, 7ull); + Block temp[8]; + + std::size_t idx = 0; + for (; idx < length; idx += step) { + temp[0] = (counter_block + b0) ^ round_key[0]; + temp[1] = (counter_block + b1) ^ round_key[0]; + temp[2] = (counter_block + b2) ^ round_key[0]; + temp[3] = (counter_block + b3) ^ round_key[0]; + temp[4] = (counter_block + b4) ^ round_key[0]; + temp[5] = (counter_block + b5) ^ round_key[0]; + temp[6] = (counter_block + b6) ^ round_key[0]; + temp[7] = (counter_block + b7) ^ round_key[0]; + counter_block = counter_block + _mm_set_epi64x(0, step); + + temp[0] = _mm_aesenc_si128(temp[0], round_key[1]); + temp[1] = _mm_aesenc_si128(temp[1], round_key[1]); + temp[2] = _mm_aesenc_si128(temp[2], round_key[1]); + temp[3] = _mm_aesenc_si128(temp[3], round_key[1]); + temp[4] = _mm_aesenc_si128(temp[4], round_key[1]); + temp[5] = _mm_aesenc_si128(temp[5], round_key[1]); + temp[6] = _mm_aesenc_si128(temp[6], round_key[1]); + temp[7] = _mm_aesenc_si128(temp[7], round_key[1]); + + temp[0] = _mm_aesenc_si128(temp[0], round_key[2]); + temp[1] = _mm_aesenc_si128(temp[1], round_key[2]); + temp[2] = _mm_aesenc_si128(temp[2], round_key[2]); + temp[3] = _mm_aesenc_si128(temp[3], round_key[2]); + temp[4] = _mm_aesenc_si128(temp[4], round_key[2]); + temp[5] = _mm_aesenc_si128(temp[5], round_key[2]); + temp[6] = _mm_aesenc_si128(temp[6], round_key[2]); + temp[7] = _mm_aesenc_si128(temp[7], round_key[2]); + + temp[0] = _mm_aesenc_si128(temp[0], round_key[3]); + temp[1] = _mm_aesenc_si128(temp[1], round_key[3]); + temp[2] = _mm_aesenc_si128(temp[2], round_key[3]); + temp[3] = _mm_aesenc_si128(temp[3], round_key[3]); + temp[4] = _mm_aesenc_si128(temp[4], round_key[3]); + temp[5] = _mm_aesenc_si128(temp[5], round_key[3]); + temp[6] = _mm_aesenc_si128(temp[6], round_key[3]); + temp[7] = _mm_aesenc_si128(temp[7], round_key[3]); + + temp[0] = _mm_aesenc_si128(temp[0], round_key[4]); + temp[1] = _mm_aesenc_si128(temp[1], round_key[4]); + temp[2] = _mm_aesenc_si128(temp[2], round_key[4]); + temp[3] = _mm_aesenc_si128(temp[3], round_key[4]); + temp[4] = _mm_aesenc_si128(temp[4], round_key[4]); + temp[5] = _mm_aesenc_si128(temp[5], round_key[4]); + temp[6] = _mm_aesenc_si128(temp[6], round_key[4]); + temp[7] = _mm_aesenc_si128(temp[7], round_key[4]); + + temp[0] = _mm_aesenc_si128(temp[0], round_key[5]); + temp[1] = _mm_aesenc_si128(temp[1], round_key[5]); + temp[2] = _mm_aesenc_si128(temp[2], round_key[5]); + temp[3] = _mm_aesenc_si128(temp[3], round_key[5]); + temp[4] = _mm_aesenc_si128(temp[4], round_key[5]); + temp[5] = _mm_aesenc_si128(temp[5], round_key[5]); + temp[6] = _mm_aesenc_si128(temp[6], round_key[5]); + temp[7] = _mm_aesenc_si128(temp[7], round_key[5]); + + temp[0] = _mm_aesenc_si128(temp[0], round_key[6]); + temp[1] = _mm_aesenc_si128(temp[1], round_key[6]); + temp[2] = _mm_aesenc_si128(temp[2], round_key[6]); + temp[3] = _mm_aesenc_si128(temp[3], round_key[6]); + temp[4] = _mm_aesenc_si128(temp[4], round_key[6]); + temp[5] = _mm_aesenc_si128(temp[5], round_key[6]); + temp[6] = _mm_aesenc_si128(temp[6], round_key[6]); + temp[7] = _mm_aesenc_si128(temp[7], round_key[6]); + + temp[0] = _mm_aesenc_si128(temp[0], round_key[7]); + temp[1] = _mm_aesenc_si128(temp[1], round_key[7]); + temp[2] = _mm_aesenc_si128(temp[2], round_key[7]); + temp[3] = _mm_aesenc_si128(temp[3], round_key[7]); + temp[4] = _mm_aesenc_si128(temp[4], round_key[7]); + temp[5] = _mm_aesenc_si128(temp[5], round_key[7]); + temp[6] = _mm_aesenc_si128(temp[6], round_key[7]); + temp[7] = _mm_aesenc_si128(temp[7], round_key[7]); + + temp[0] = _mm_aesenc_si128(temp[0], round_key[8]); + temp[1] = _mm_aesenc_si128(temp[1], round_key[8]); + temp[2] = _mm_aesenc_si128(temp[2], round_key[8]); + temp[3] = _mm_aesenc_si128(temp[3], round_key[8]); + temp[4] = _mm_aesenc_si128(temp[4], round_key[8]); + temp[5] = _mm_aesenc_si128(temp[5], round_key[8]); + temp[6] = _mm_aesenc_si128(temp[6], round_key[8]); + temp[7] = _mm_aesenc_si128(temp[7], round_key[8]); + + temp[0] = _mm_aesenc_si128(temp[0], round_key[9]); + temp[1] = _mm_aesenc_si128(temp[1], round_key[9]); + temp[2] = _mm_aesenc_si128(temp[2], round_key[9]); + temp[3] = _mm_aesenc_si128(temp[3], round_key[9]); + temp[4] = _mm_aesenc_si128(temp[4], round_key[9]); + temp[5] = _mm_aesenc_si128(temp[5], round_key[9]); + temp[6] = _mm_aesenc_si128(temp[6], round_key[9]); + temp[7] = _mm_aesenc_si128(temp[7], round_key[9]); + + temp[0] = _mm_aesenclast_si128(temp[0], round_key[10]); + temp[1] = _mm_aesenclast_si128(temp[1], round_key[10]); + temp[2] = _mm_aesenclast_si128(temp[2], round_key[10]); + temp[3] = _mm_aesenclast_si128(temp[3], round_key[10]); + temp[4] = _mm_aesenclast_si128(temp[4], round_key[10]); + temp[5] = _mm_aesenclast_si128(temp[5], round_key[10]); + temp[6] = _mm_aesenclast_si128(temp[6], round_key[10]); + temp[7] = _mm_aesenclast_si128(temp[7], round_key[10]); + + memcpy(out + idx, temp, sizeof(temp)); + } + + for (; idx < out_block_count; idx++) { + auto left = counter_block ^ round_key[0]; + counter_block = counter_block + _mm_set_epi64x(0, 1); + left = _mm_aesenc_si128(left, round_key[1]); + left = _mm_aesenc_si128(left, round_key[2]); + left = _mm_aesenc_si128(left, round_key[3]); + left = _mm_aesenc_si128(left, round_key[4]); + left = _mm_aesenc_si128(left, round_key[5]); + left = _mm_aesenc_si128(left, round_key[6]); + left = _mm_aesenc_si128(left, round_key[7]); + left = _mm_aesenc_si128(left, round_key[8]); + left = _mm_aesenc_si128(left, round_key[9]); + left = _mm_aesenclast_si128(left, round_key[10]); + + memcpy(out + idx, &left, sizeof(left)); + } +} + +} // namespace aes +} // namespace util +} // namespace solo +} // namespace petace +#endif diff --git a/src/solo/util/aes_ecb_ctr.h b/src/solo/util/aes_ecb_ctr.h new file mode 100644 index 0000000..b2a4dd8 --- /dev/null +++ b/src/solo/util/aes_ecb_ctr.h @@ -0,0 +1,45 @@ +// Copyright 2023 TikTok Pte. Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include "solo/util/config.h" +#ifdef SOLO_USE_AES_INTRIN + +#include + +#include +#include +#include +#include + +#include "solo/util/defines.h" + +namespace petace { +namespace solo { +namespace util { +namespace aes { + +typedef __m128i Block; +typedef __m128i RoundKey[11]; + +void set_round_key(const Block& key, RoundKey& round_key); + +void encrypt_ecb_ctr(const RoundKey& round_key, std::uint64_t counter, Block* out, std::size_t out_block_count); + +} // namespace aes +} // namespace util +} // namespace solo +} // namespace petace +#endif diff --git a/src/solo/util/blake2-impl.h b/src/solo/util/blake2-impl.h new file mode 100644 index 0000000..47488cb --- /dev/null +++ b/src/solo/util/blake2-impl.h @@ -0,0 +1,136 @@ +/* + BLAKE2 reference source code package - reference C implementations + + Copyright 2012, Samuel Neves . You may use this under the + terms of the CC0, the OpenSSL Licence, or the Apache Public License 2.0, at + your option. The terms of these licenses can be found at: + + - CC0 1.0 Universal : http://creativecommons.org/publicdomain/zero/1.0 + - OpenSSL license : https://www.openssl.org/source/license.html + - Apache 2.0 : http://www.apache.org/licenses/LICENSE-2.0 + + More information about the BLAKE2 hash function can be found at + https://blake2.net. +*/ +#ifndef BLAKE2_IMPL_H +#define BLAKE2_IMPL_H + +#include +#include + +#if !defined(__cplusplus) && (!defined(__STDC_VERSION__) || __STDC_VERSION__ < 199901L) +#if defined(_MSC_VER) +#define BLAKE2_INLINE __inline +#elif defined(__GNUC__) +#define BLAKE2_INLINE __inline__ +#else +#define BLAKE2_INLINE +#endif +#else +#define BLAKE2_INLINE inline +#endif + +static BLAKE2_INLINE uint32_t load32(const void* src) { +#if defined(NATIVE_LITTLE_ENDIAN) + uint32_t w; + memcpy(&w, src, sizeof w); + return w; +#else + const uint8_t* p = (const uint8_t*)src; + return ((uint32_t)(p[0]) << 0) | ((uint32_t)(p[1]) << 8) | ((uint32_t)(p[2]) << 16) | ((uint32_t)(p[3]) << 24); +#endif +} + +static BLAKE2_INLINE uint64_t load64(const void* src) { +#if defined(NATIVE_LITTLE_ENDIAN) + uint64_t w; + memcpy(&w, src, sizeof w); + return w; +#else + const uint8_t* p = (const uint8_t*)src; + return ((uint64_t)(p[0]) << 0) | ((uint64_t)(p[1]) << 8) | ((uint64_t)(p[2]) << 16) | ((uint64_t)(p[3]) << 24) | + ((uint64_t)(p[4]) << 32) | ((uint64_t)(p[5]) << 40) | ((uint64_t)(p[6]) << 48) | ((uint64_t)(p[7]) << 56); +#endif +} + +static BLAKE2_INLINE uint16_t load16(const void* src) { +#if defined(NATIVE_LITTLE_ENDIAN) + uint16_t w; + memcpy(&w, src, sizeof w); + return w; +#else + const uint8_t* p = (const uint8_t*)src; + return (uint16_t)(((uint32_t)(p[0]) << 0) | ((uint32_t)(p[1]) << 8)); +#endif +} + +static BLAKE2_INLINE void store16(void* dst, uint16_t w) { +#if defined(NATIVE_LITTLE_ENDIAN) + memcpy(dst, &w, sizeof w); +#else + uint8_t* p = (uint8_t*)dst; + *p++ = (uint8_t)w; + w >>= 8; + *p++ = (uint8_t)w; +#endif +} + +static BLAKE2_INLINE void store32(void* dst, uint32_t w) { +#if defined(NATIVE_LITTLE_ENDIAN) + memcpy(dst, &w, sizeof w); +#else + uint8_t* p = (uint8_t*)dst; + p[0] = (uint8_t)(w >> 0); + p[1] = (uint8_t)(w >> 8); + p[2] = (uint8_t)(w >> 16); + p[3] = (uint8_t)(w >> 24); +#endif +} + +static BLAKE2_INLINE void store64(void* dst, uint64_t w) { +#if defined(NATIVE_LITTLE_ENDIAN) + memcpy(dst, &w, sizeof w); +#else + uint8_t* p = (uint8_t*)dst; + p[0] = (uint8_t)(w >> 0); + p[1] = (uint8_t)(w >> 8); + p[2] = (uint8_t)(w >> 16); + p[3] = (uint8_t)(w >> 24); + p[4] = (uint8_t)(w >> 32); + p[5] = (uint8_t)(w >> 40); + p[6] = (uint8_t)(w >> 48); + p[7] = (uint8_t)(w >> 56); +#endif +} + +static BLAKE2_INLINE uint64_t load48(const void* src) { + const uint8_t* p = (const uint8_t*)src; + return ((uint64_t)(p[0]) << 0) | ((uint64_t)(p[1]) << 8) | ((uint64_t)(p[2]) << 16) | ((uint64_t)(p[3]) << 24) | + ((uint64_t)(p[4]) << 32) | ((uint64_t)(p[5]) << 40); +} + +static BLAKE2_INLINE void store48(void* dst, uint64_t w) { + uint8_t* p = (uint8_t*)dst; + p[0] = (uint8_t)(w >> 0); + p[1] = (uint8_t)(w >> 8); + p[2] = (uint8_t)(w >> 16); + p[3] = (uint8_t)(w >> 24); + p[4] = (uint8_t)(w >> 32); + p[5] = (uint8_t)(w >> 40); +} + +static BLAKE2_INLINE uint32_t rotr32(const uint32_t w, const unsigned c) { + return (w >> c) | (w << (32 - c)); +} + +static BLAKE2_INLINE uint64_t rotr64(const uint64_t w, const unsigned c) { + return (w >> c) | (w << (64 - c)); +} + +/* prevents compiler optimizing out memset() */ +static BLAKE2_INLINE void secure_zero_memory(void* v, size_t n) { + static void* (*const volatile memset_v)(void*, int, size_t) = &memset; + memset_v(v, 0, n); +} + +#endif diff --git a/src/solo/util/blake2.h b/src/solo/util/blake2.h new file mode 100644 index 0000000..eb1d071 --- /dev/null +++ b/src/solo/util/blake2.h @@ -0,0 +1,184 @@ +/* + BLAKE2 reference source code package - reference C implementations + + Copyright 2012, Samuel Neves . You may use this under the + terms of the CC0, the OpenSSL Licence, or the Apache Public License 2.0, at + your option. The terms of these licenses can be found at: + + - CC0 1.0 Universal : http://creativecommons.org/publicdomain/zero/1.0 + - OpenSSL license : https://www.openssl.org/source/license.html + - Apache 2.0 : http://www.apache.org/licenses/LICENSE-2.0 + + More information about the BLAKE2 hash function can be found at + https://blake2.net. +*/ +#ifndef BLAKE2_H +#define BLAKE2_H + +#include +#include + +#if defined(_MSC_VER) +#define BLAKE2_PACKED(x) __pragma(pack(push, 1)) x __pragma(pack(pop)) +#else +#define BLAKE2_PACKED(x) x __attribute__((packed)) +#endif + +#if defined(__cplusplus) +extern "C" { +#endif + +enum blake2s_constant { + BLAKE2S_BLOCKBYTES = 64, + BLAKE2S_OUTBYTES = 32, + BLAKE2S_KEYBYTES = 32, + BLAKE2S_SALTBYTES = 8, + BLAKE2S_PERSONALBYTES = 8 +}; + +enum blake2b_constant { + BLAKE2B_BLOCKBYTES = 128, + BLAKE2B_OUTBYTES = 64, + BLAKE2B_KEYBYTES = 64, + BLAKE2B_SALTBYTES = 16, + BLAKE2B_PERSONALBYTES = 16 +}; + +typedef struct blake2s_state__ { + uint32_t h[8]; + uint32_t t[2]; + uint32_t f[2]; + uint8_t buf[BLAKE2S_BLOCKBYTES]; + size_t buflen; + size_t outlen; + uint8_t last_node; +} blake2s_state; + +typedef struct blake2b_state__ { + uint64_t h[8]; + uint64_t t[2]; + uint64_t f[2]; + uint8_t buf[BLAKE2B_BLOCKBYTES]; + size_t buflen; + size_t outlen; + uint8_t last_node; +} blake2b_state; + +typedef struct blake2sp_state__ { + blake2s_state S[8][1]; + blake2s_state R[1]; + uint8_t buf[8 * BLAKE2S_BLOCKBYTES]; + size_t buflen; + size_t outlen; +} blake2sp_state; + +typedef struct blake2bp_state__ { + blake2b_state S[4][1]; + blake2b_state R[1]; + uint8_t buf[4 * BLAKE2B_BLOCKBYTES]; + size_t buflen; + size_t outlen; +} blake2bp_state; + +BLAKE2_PACKED(struct blake2s_param__ { + uint8_t digest_length; /* 1 */ + uint8_t key_length; /* 2 */ + uint8_t fanout; /* 3 */ + uint8_t depth; /* 4 */ + uint32_t leaf_length; /* 8 */ + uint32_t node_offset; /* 12 */ + uint16_t xof_length; /* 14 */ + uint8_t node_depth; /* 15 */ + uint8_t inner_length; /* 16 */ + /* uint8_t reserved[0]; */ + uint8_t salt[BLAKE2S_SALTBYTES]; /* 24 */ + uint8_t personal[BLAKE2S_PERSONALBYTES]; /* 32 */ +}); + +typedef struct blake2s_param__ blake2s_param; + +BLAKE2_PACKED(struct blake2b_param__ { + uint8_t digest_length; /* 1 */ + uint8_t key_length; /* 2 */ + uint8_t fanout; /* 3 */ + uint8_t depth; /* 4 */ + uint32_t leaf_length; /* 8 */ + uint32_t node_offset; /* 12 */ + uint32_t xof_length; /* 16 */ + uint8_t node_depth; /* 17 */ + uint8_t inner_length; /* 18 */ + uint8_t reserved[14]; /* 32 */ + uint8_t salt[BLAKE2B_SALTBYTES]; /* 48 */ + uint8_t personal[BLAKE2B_PERSONALBYTES]; /* 64 */ +}); + +typedef struct blake2b_param__ blake2b_param; + +typedef struct blake2xs_state__ { + blake2s_state S[1]; + blake2s_param P[1]; +} blake2xs_state; + +typedef struct blake2xb_state__ { + blake2b_state S[1]; + blake2b_param P[1]; +} blake2xb_state; + +/* Padded structs result in a compile-time error */ +enum { + BLAKE2_DUMMY_1 = 1 / (int)(sizeof(blake2s_param) == BLAKE2S_OUTBYTES), + BLAKE2_DUMMY_2 = 1 / (int)(sizeof(blake2b_param) == BLAKE2B_OUTBYTES) +}; + +/* Streaming API */ +int blake2s_init(blake2s_state* S, size_t outlen); +int blake2s_init_key(blake2s_state* S, size_t outlen, const void* key, size_t keylen); +int blake2s_init_param(blake2s_state* S, const blake2s_param* P); +int blake2s_update(blake2s_state* S, const void* in, size_t inlen); +int blake2s_final(blake2s_state* S, void* out, size_t outlen); + +int blake2b_init(blake2b_state* S, size_t outlen); +int blake2b_init_key(blake2b_state* S, size_t outlen, const void* key, size_t keylen); +int blake2b_init_param(blake2b_state* S, const blake2b_param* P); +int blake2b_update(blake2b_state* S, const void* in, size_t inlen); +int blake2b_final(blake2b_state* S, void* out, size_t outlen); + +int blake2sp_init(blake2sp_state* S, size_t outlen); +int blake2sp_init_key(blake2sp_state* S, size_t outlen, const void* key, size_t keylen); +int blake2sp_update(blake2sp_state* S, const void* in, size_t inlen); +int blake2sp_final(blake2sp_state* S, void* out, size_t outlen); + +int blake2bp_init(blake2bp_state* S, size_t outlen); +int blake2bp_init_key(blake2bp_state* S, size_t outlen, const void* key, size_t keylen); +int blake2bp_update(blake2bp_state* S, const void* in, size_t inlen); +int blake2bp_final(blake2bp_state* S, void* out, size_t outlen); + +/* Variable output length API */ +int blake2xs_init(blake2xs_state* S, const size_t outlen); +int blake2xs_init_key(blake2xs_state* S, const size_t outlen, const void* key, size_t keylen); +int blake2xs_update(blake2xs_state* S, const void* in, size_t inlen); +int blake2xs_final(blake2xs_state* S, void* out, size_t outlen); + +int blake2xb_init(blake2xb_state* S, const size_t outlen); +int blake2xb_init_key(blake2xb_state* S, const size_t outlen, const void* key, size_t keylen); +int blake2xb_update(blake2xb_state* S, const void* in, size_t inlen); +int blake2xb_final(blake2xb_state* S, void* out, size_t outlen); + +/* Simple API */ +int blake2s(void* out, size_t outlen, const void* in, size_t inlen, const void* key, size_t keylen); +int blake2b(void* out, size_t outlen, const void* in, size_t inlen, const void* key, size_t keylen); + +int blake2sp(void* out, size_t outlen, const void* in, size_t inlen, const void* key, size_t keylen); +int blake2bp(void* out, size_t outlen, const void* in, size_t inlen, const void* key, size_t keylen); + +int blake2xs(void* out, size_t outlen, const void* in, size_t inlen, const void* key, size_t keylen); +int blake2xb(void* out, size_t outlen, const void* in, size_t inlen, const void* key, size_t keylen); + +/* This is simply an alias for blake2b */ +int blake2(void* out, size_t outlen, const void* in, size_t inlen, const void* key, size_t keylen); + +#if defined(__cplusplus) +} +#endif + +#endif diff --git a/src/solo/util/blake2b-ref.c b/src/solo/util/blake2b-ref.c new file mode 100644 index 0000000..b9b2dce --- /dev/null +++ b/src/solo/util/blake2b-ref.c @@ -0,0 +1,357 @@ +/* + BLAKE2 reference source code package - reference C implementations + + Copyright 2012, Samuel Neves . You may use this under the + terms of the CC0, the OpenSSL Licence, or the Apache Public License 2.0, at + your option. The terms of these licenses can be found at: + + - CC0 1.0 Universal : http://creativecommons.org/publicdomain/zero/1.0 + - OpenSSL license : https://www.openssl.org/source/license.html + - Apache 2.0 : http://www.apache.org/licenses/LICENSE-2.0 + + More information about the BLAKE2 hash function can be found at + https://blake2.net. +*/ + +#include +#include +#include + +#include "blake2-impl.h" +#include "blake2.h" + +static const uint64_t blake2b_IV[8] = {0x6a09e667f3bcc908ULL, 0xbb67ae8584caa73bULL, 0x3c6ef372fe94f82bULL, + 0xa54ff53a5f1d36f1ULL, 0x510e527fade682d1ULL, 0x9b05688c2b3e6c1fULL, 0x1f83d9abfb41bd6bULL, + 0x5be0cd19137e2179ULL}; + +static const uint8_t blake2b_sigma[12][16] = {{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, + {14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3}, {11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4}, + {7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8}, {9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13}, + {2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9}, {12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11}, + {13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10}, {6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5}, + {10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0}, {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, + {14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3}}; + +static void blake2b_set_lastnode(blake2b_state* S) { + S->f[1] = (uint64_t)-1; +} + +/* Some helper functions, not necessarily useful */ +static int blake2b_is_lastblock(const blake2b_state* S) { + return S->f[0] != 0; +} + +static void blake2b_set_lastblock(blake2b_state* S) { + if (S->last_node) + blake2b_set_lastnode(S); + + S->f[0] = (uint64_t)-1; +} + +static void blake2b_increment_counter(blake2b_state* S, const uint64_t inc) { + S->t[0] += inc; + S->t[1] += (S->t[0] < inc); +} + +static void blake2b_init0(blake2b_state* S) { + size_t i; + memset(S, 0, sizeof(blake2b_state)); + + for (i = 0; i < 8; ++i) + S->h[i] = blake2b_IV[i]; +} + +/* init xors IV with input parameter block */ +int blake2b_init_param(blake2b_state* S, const blake2b_param* P) { + const uint8_t* p = (const uint8_t*)(P); + size_t i; + + blake2b_init0(S); + + /* IV XOR ParamBlock */ + for (i = 0; i < 8; ++i) + S->h[i] ^= load64(p + sizeof(S->h[i]) * i); + + S->outlen = P->digest_length; + return 0; +} + +int blake2b_init(blake2b_state* S, size_t outlen) { + blake2b_param P[1]; + + if ((!outlen) || (outlen > BLAKE2B_OUTBYTES)) + return -1; + + P->digest_length = (uint8_t)outlen; + P->key_length = 0; + P->fanout = 1; + P->depth = 1; + store32(&P->leaf_length, 0); + store32(&P->node_offset, 0); + store32(&P->xof_length, 0); + P->node_depth = 0; + P->inner_length = 0; + memset(P->reserved, 0, sizeof(P->reserved)); + memset(P->salt, 0, sizeof(P->salt)); + memset(P->personal, 0, sizeof(P->personal)); + return blake2b_init_param(S, P); +} + +int blake2b_init_key(blake2b_state* S, size_t outlen, const void* key, size_t keylen) { + blake2b_param P[1]; + + if ((!outlen) || (outlen > BLAKE2B_OUTBYTES)) + return -1; + + if (!key || !keylen || keylen > BLAKE2B_KEYBYTES) + return -1; + + P->digest_length = (uint8_t)outlen; + P->key_length = (uint8_t)keylen; + P->fanout = 1; + P->depth = 1; + store32(&P->leaf_length, 0); + store32(&P->node_offset, 0); + store32(&P->xof_length, 0); + P->node_depth = 0; + P->inner_length = 0; + memset(P->reserved, 0, sizeof(P->reserved)); + memset(P->salt, 0, sizeof(P->salt)); + memset(P->personal, 0, sizeof(P->personal)); + + if (blake2b_init_param(S, P) < 0) + return -1; + + { + uint8_t block[BLAKE2B_BLOCKBYTES]; + memset(block, 0, BLAKE2B_BLOCKBYTES); + memcpy(block, key, keylen); + blake2b_update(S, block, BLAKE2B_BLOCKBYTES); + secure_zero_memory(block, BLAKE2B_BLOCKBYTES); /* Burn the key from stack */ + } + return 0; +} + +#define G(r, i, a, b, c, d) \ + do { \ + a = a + b + m[blake2b_sigma[r][2 * i + 0]]; \ + d = rotr64(d ^ a, 32); \ + c = c + d; \ + b = rotr64(b ^ c, 24); \ + a = a + b + m[blake2b_sigma[r][2 * i + 1]]; \ + d = rotr64(d ^ a, 16); \ + c = c + d; \ + b = rotr64(b ^ c, 63); \ + } while (0) + +#define ROUND(r) \ + do { \ + G(r, 0, v[0], v[4], v[8], v[12]); \ + G(r, 1, v[1], v[5], v[9], v[13]); \ + G(r, 2, v[2], v[6], v[10], v[14]); \ + G(r, 3, v[3], v[7], v[11], v[15]); \ + G(r, 4, v[0], v[5], v[10], v[15]); \ + G(r, 5, v[1], v[6], v[11], v[12]); \ + G(r, 6, v[2], v[7], v[8], v[13]); \ + G(r, 7, v[3], v[4], v[9], v[14]); \ + } while (0) + +static void blake2b_compress(blake2b_state* S, const uint8_t block[BLAKE2B_BLOCKBYTES]) { + uint64_t m[16]; + uint64_t v[16]; + size_t i; + + for (i = 0; i < 16; ++i) { + m[i] = load64(block + i * sizeof(m[i])); + } + + for (i = 0; i < 8; ++i) { + v[i] = S->h[i]; + } + + v[8] = blake2b_IV[0]; + v[9] = blake2b_IV[1]; + v[10] = blake2b_IV[2]; + v[11] = blake2b_IV[3]; + v[12] = blake2b_IV[4] ^ S->t[0]; + v[13] = blake2b_IV[5] ^ S->t[1]; + v[14] = blake2b_IV[6] ^ S->f[0]; + v[15] = blake2b_IV[7] ^ S->f[1]; + + ROUND(0); + ROUND(1); + ROUND(2); + ROUND(3); + ROUND(4); + ROUND(5); + ROUND(6); + ROUND(7); + ROUND(8); + ROUND(9); + ROUND(10); + ROUND(11); + + for (i = 0; i < 8; ++i) { + S->h[i] = S->h[i] ^ v[i] ^ v[i + 8]; + } +} + +#undef G +#undef ROUND + +int blake2b_update(blake2b_state* S, const void* pin, size_t inlen) { + const unsigned char* in = (const unsigned char*)pin; + if (inlen > 0) { + size_t left = S->buflen; + size_t fill = BLAKE2B_BLOCKBYTES - left; + if (inlen > fill) { + S->buflen = 0; + memcpy(S->buf + left, in, fill); /* Fill buffer */ + blake2b_increment_counter(S, BLAKE2B_BLOCKBYTES); + blake2b_compress(S, S->buf); /* Compress */ + in += fill; + inlen -= fill; + while (inlen > BLAKE2B_BLOCKBYTES) { + blake2b_increment_counter(S, BLAKE2B_BLOCKBYTES); + blake2b_compress(S, in); + in += BLAKE2B_BLOCKBYTES; + inlen -= BLAKE2B_BLOCKBYTES; + } + } + memcpy(S->buf + S->buflen, in, inlen); + S->buflen += inlen; + } + return 0; +} + +int blake2b_final(blake2b_state* S, void* out, size_t outlen) { + uint8_t buffer[BLAKE2B_OUTBYTES] = {0}; + size_t i; + + if (out == NULL || outlen < S->outlen) + return -1; + + if (blake2b_is_lastblock(S)) + return -1; + + blake2b_increment_counter(S, S->buflen); + blake2b_set_lastblock(S); + memset(S->buf + S->buflen, 0, BLAKE2B_BLOCKBYTES - S->buflen); /* Padding */ + blake2b_compress(S, S->buf); + + for (i = 0; i < 8; ++i) /* Output full hash to temp buffer */ + store64(buffer + sizeof(S->h[i]) * i, S->h[i]); + + memcpy(out, buffer, S->outlen); + secure_zero_memory(buffer, sizeof(buffer)); + return 0; +} + +/* inlen, at least, should be uint64_t. Others can be size_t. */ +int blake2b(void* out, size_t outlen, const void* in, size_t inlen, const void* key, size_t keylen) { + blake2b_state S[1]; + + /* Verify parameters */ + if (NULL == in && inlen > 0) + return -1; + + if (NULL == out) + return -1; + + if (NULL == key && keylen > 0) + return -1; + + if (!outlen || outlen > BLAKE2B_OUTBYTES) + return -1; + + if (keylen > BLAKE2B_KEYBYTES) + return -1; + + if (keylen > 0) { + if (blake2b_init_key(S, outlen, key, keylen) < 0) + return -1; + } else { + if (blake2b_init(S, outlen) < 0) + return -1; + } + + blake2b_update(S, (const uint8_t*)in, inlen); + blake2b_final(S, out, outlen); + return 0; +} + +int blake2(void* out, size_t outlen, const void* in, size_t inlen, const void* key, size_t keylen) { + return blake2b(out, outlen, in, inlen, key, keylen); +} + +#if defined(SUPERCOP) +int crypto_hash(unsigned char* out, unsigned char* in, unsigned long long inlen) { + return blake2b(out, BLAKE2B_OUTBYTES, in, inlen, NULL, 0); +} +#endif + +#if defined(BLAKE2B_SELFTEST) +#include + +#include "blake2-kat.h" +int main(void) { + uint8_t key[BLAKE2B_KEYBYTES]; + uint8_t buf[BLAKE2_KAT_LENGTH]; + size_t i, step; + + for (i = 0; i < BLAKE2B_KEYBYTES; ++i) + key[i] = (uint8_t)i; + + for (i = 0; i < BLAKE2_KAT_LENGTH; ++i) + buf[i] = (uint8_t)i; + + /* Test simple API */ + for (i = 0; i < BLAKE2_KAT_LENGTH; ++i) { + uint8_t hash[BLAKE2B_OUTBYTES]; + blake2b(hash, BLAKE2B_OUTBYTES, buf, i, key, BLAKE2B_KEYBYTES); + + if (0 != memcmp(hash, blake2b_keyed_kat[i], BLAKE2B_OUTBYTES)) { + goto fail; + } + } + + /* Test streaming API */ + for (step = 1; step < BLAKE2B_BLOCKBYTES; ++step) { + for (i = 0; i < BLAKE2_KAT_LENGTH; ++i) { + uint8_t hash[BLAKE2B_OUTBYTES]; + blake2b_state S; + uint8_t* p = buf; + size_t mlen = i; + int err = 0; + + if ((err = blake2b_init_key(&S, BLAKE2B_OUTBYTES, key, BLAKE2B_KEYBYTES)) < 0) { + goto fail; + } + + while (mlen >= step) { + if ((err = blake2b_update(&S, p, step)) < 0) { + goto fail; + } + mlen -= step; + p += step; + } + if ((err = blake2b_update(&S, p, mlen)) < 0) { + goto fail; + } + if ((err = blake2b_final(&S, hash, BLAKE2B_OUTBYTES)) < 0) { + goto fail; + } + + if (0 != memcmp(hash, blake2b_keyed_kat[i], BLAKE2B_OUTBYTES)) { + goto fail; + } + } + } + + puts("ok"); + return 0; +fail: + puts("error"); + return -1; +} +#endif diff --git a/src/solo/util/blake2xb-ref.c b/src/solo/util/blake2xb-ref.c new file mode 100644 index 0000000..b33aa27 --- /dev/null +++ b/src/solo/util/blake2xb-ref.c @@ -0,0 +1,235 @@ +/* + BLAKE2 reference source code package - reference C implementations + + Copyright 2016, JP Aumasson . + Copyright 2016, Samuel Neves . + + You may use this under the terms of the CC0, the OpenSSL Licence, or + the Apache Public License 2.0, at your option. The terms of these + licenses can be found at: + + - CC0 1.0 Universal : http://creativecommons.org/publicdomain/zero/1.0 + - OpenSSL license : https://www.openssl.org/source/license.html + - Apache 2.0 : http://www.apache.org/licenses/LICENSE-2.0 + + More information about the BLAKE2 hash function can be found at + https://blake2.net. +*/ + +#include +#include +#include + +#include "blake2-impl.h" +#include "blake2.h" + +int blake2xb_init(blake2xb_state* S, const size_t outlen) { + return blake2xb_init_key(S, outlen, NULL, 0); +} + +int blake2xb_init_key(blake2xb_state* S, const size_t outlen, const void* key, size_t keylen) { + if (outlen == 0 || outlen > 0xFFFFFFFFUL) { + return -1; + } + + if (NULL != key && keylen > BLAKE2B_KEYBYTES) { + return -1; + } + + if (NULL == key && keylen > 0) { + return -1; + } + + /* Initialize parameter block */ + S->P->digest_length = BLAKE2B_OUTBYTES; + S->P->key_length = keylen; + S->P->fanout = 1; + S->P->depth = 1; + store32(&S->P->leaf_length, 0); + store32(&S->P->node_offset, 0); + store32(&S->P->xof_length, outlen); + S->P->node_depth = 0; + S->P->inner_length = 0; + memset(S->P->reserved, 0, sizeof(S->P->reserved)); + memset(S->P->salt, 0, sizeof(S->P->salt)); + memset(S->P->personal, 0, sizeof(S->P->personal)); + + if (blake2b_init_param(S->S, S->P) < 0) { + return -1; + } + + if (keylen > 0) { + uint8_t block[BLAKE2B_BLOCKBYTES]; + memset(block, 0, BLAKE2B_BLOCKBYTES); + memcpy(block, key, keylen); + blake2b_update(S->S, block, BLAKE2B_BLOCKBYTES); + secure_zero_memory(block, BLAKE2B_BLOCKBYTES); + } + return 0; +} + +int blake2xb_update(blake2xb_state* S, const void* in, size_t inlen) { + return blake2b_update(S->S, in, inlen); +} + +int blake2xb_final(blake2xb_state* S, void* out, size_t outlen) { + blake2b_state C[1]; + blake2b_param P[1]; + uint32_t xof_length = load32(&S->P->xof_length); + uint8_t root[BLAKE2B_BLOCKBYTES]; + size_t i; + + if (NULL == out) { + return -1; + } + + /* outlen must match the output size defined in xof_length, */ + /* unless it was -1, in which case anything goes except 0. */ + if (xof_length == 0xFFFFFFFFUL) { + if (outlen == 0) { + return -1; + } + } else { + if (outlen != xof_length) { + return -1; + } + } + + /* Finalize the root hash */ + if (blake2b_final(S->S, root, BLAKE2B_OUTBYTES) < 0) { + return -1; + } + + /* Set common block structure values */ + /* Copy values from parent instance, and only change the ones below */ + memcpy(P, S->P, sizeof(blake2b_param)); + P->key_length = 0; + P->fanout = 0; + P->depth = 0; + store32(&P->leaf_length, BLAKE2B_OUTBYTES); + P->inner_length = BLAKE2B_OUTBYTES; + P->node_depth = 0; + + for (i = 0; outlen > 0; ++i) { + const size_t block_size = (outlen < BLAKE2B_OUTBYTES) ? outlen : BLAKE2B_OUTBYTES; + /* Initialize state */ + P->digest_length = block_size; + store32(&P->node_offset, i); + blake2b_init_param(C, P); + /* Process key if needed */ + blake2b_update(C, root, BLAKE2B_OUTBYTES); + if (blake2b_final(C, (uint8_t*)out + i * BLAKE2B_OUTBYTES, block_size) < 0) { + return -1; + } + outlen -= block_size; + } + secure_zero_memory(root, sizeof(root)); + secure_zero_memory(P, sizeof(P)); + secure_zero_memory(C, sizeof(C)); + /* Put blake2xb in an invalid state? cf. blake2s_is_lastblock */ + return 0; +} + +int blake2xb(void* out, size_t outlen, const void* in, size_t inlen, const void* key, size_t keylen) { + blake2xb_state S[1]; + + /* Verify parameters */ + if (NULL == in && inlen > 0) + return -1; + + if (NULL == out) + return -1; + + if (NULL == key && keylen > 0) + return -1; + + if (keylen > BLAKE2B_KEYBYTES) + return -1; + + if (outlen == 0) + return -1; + + /* Initialize the root block structure */ + if (blake2xb_init_key(S, outlen, key, keylen) < 0) { + return -1; + } + + /* Absorb the input message */ + blake2xb_update(S, in, inlen); + + /* Compute the root node of the tree and the final hash using the counter construction */ + return blake2xb_final(S, out, outlen); +} + +#if defined(BLAKE2XB_SELFTEST) +#include + +#include "blake2-kat.h" +int main(void) { + uint8_t key[BLAKE2B_KEYBYTES]; + uint8_t buf[BLAKE2_KAT_LENGTH]; + size_t i, step, outlen; + + for (i = 0; i < BLAKE2B_KEYBYTES; ++i) { + key[i] = (uint8_t)i; + } + + for (i = 0; i < BLAKE2_KAT_LENGTH; ++i) { + buf[i] = (uint8_t)i; + } + + /* Testing length of outputs rather than inputs */ + /* (Test of input lengths mostly covered by blake2b tests) */ + + /* Test simple API */ + for (outlen = 1; outlen <= BLAKE2_KAT_LENGTH; ++outlen) { + uint8_t hash[BLAKE2_KAT_LENGTH] = {0}; + if (blake2xb(hash, outlen, buf, BLAKE2_KAT_LENGTH, key, BLAKE2B_KEYBYTES) < 0) { + goto fail; + } + + if (0 != memcmp(hash, blake2xb_keyed_kat[outlen - 1], outlen)) { + goto fail; + } + } + + /* Test streaming API */ + for (step = 1; step < BLAKE2B_BLOCKBYTES; ++step) { + for (outlen = 1; outlen <= BLAKE2_KAT_LENGTH; ++outlen) { + uint8_t hash[BLAKE2_KAT_LENGTH]; + blake2xb_state S; + uint8_t* p = buf; + size_t mlen = BLAKE2_KAT_LENGTH; + int err = 0; + + if ((err = blake2xb_init_key(&S, outlen, key, BLAKE2B_KEYBYTES)) < 0) { + goto fail; + } + + while (mlen >= step) { + if ((err = blake2xb_update(&S, p, step)) < 0) { + goto fail; + } + mlen -= step; + p += step; + } + if ((err = blake2xb_update(&S, p, mlen)) < 0) { + goto fail; + } + if ((err = blake2xb_final(&S, hash, outlen)) < 0) { + goto fail; + } + + if (0 != memcmp(hash, blake2xb_keyed_kat[outlen - 1], outlen)) { + goto fail; + } + } + } + + puts("ok"); + return 0; +fail: + puts("error"); + return -1; +} +#endif diff --git a/src/solo/util/config.h.in b/src/solo/util/config.h.in new file mode 100644 index 0000000..03478b9 --- /dev/null +++ b/src/solo/util/config.h.in @@ -0,0 +1,37 @@ +// Copyright 2023 TikTok Pte. Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#define SOLO_VERSION "@SOLO_VERSION@" +#define SOLO_VERSION_MAJOR @SOLO_VERSION_MAJOR@ +#define SOLO_VERSION_MINOR @SOLO_VERSION_MINOR@ +#define SOLO_VERSION_PATCH @SOLO_VERSION_PATCH@ + +// Are we in debug mode? +#cmakedefine SOLO_DEBUG + +// C++17 features +#cmakedefine SOLO_USE_STD_BYTE +#cmakedefine SOLO_USE_SHARED_MUTEX +#cmakedefine SOLO_USE_IF_CONSTEXPR +#cmakedefine SOLO_USE_MAYBE_UNUSED +#cmakedefine SOLO_USE_NODISCARD +#cmakedefine SOLO_USE_STD_FOR_EACH_N + +// Intrinsics +#cmakedefine SOLO_USE_AES_INTRIN + +// Third-party dependencies +#cmakedefine SOLO_USE_ZLIB diff --git a/src/solo/util/defines.h b/src/solo/util/defines.h new file mode 100644 index 0000000..cb49753 --- /dev/null +++ b/src/solo/util/defines.h @@ -0,0 +1,40 @@ +// Copyright 2023 TikTok Pte. Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include "solo/util/config.h" + +// Use std::byte as byte type +#ifdef SOLO_USE_STD_BYTE +#include +namespace petace { +namespace solo { +using Byte = std::byte; +} // namespace solo +} // namespace petace +#else +namespace petace { +namespace solo { +enum class Byte : unsigned char {}; +} // namespace solo +} // namespace petace +#endif + +// Use [[nodiscard]] from C++17 +#ifdef SOLO_USE_NODISCARD +#define SOLO_NODISCARD [[nodiscard]] +#else +#define SOLO_NODISCARD +#endif diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 0000000..0e8f083 --- /dev/null +++ b/test/CMakeLists.txt @@ -0,0 +1,100 @@ +# Copyright 2023 TikTok Pte. Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +cmake_minimum_required(VERSION 3.14) + +project(SOLOTest VERSION 0.1.0 LANGUAGES CXX C) + +# If not called from root CMakeLists.txt +if(NOT DEFINED SOLO_BUILD_TEST) + set(SOLO_BUILD_TEST ON) + + find_package(PETAce-Solo 0.1.0 EXACT REQUIRED) + + # Must define these variables and include macros + set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/lib) + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/bin) + set(SOLO_THIRDPARTY_DIR ${CMAKE_CURRENT_BINARY_DIR}/thirdparty) + set(THIRDPARTY_BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/thirdparty) + include(FetchContent) + mark_as_advanced(FETCHCONTENT_BASE_DIR) + mark_as_advanced(FETCHCONTENT_FULLY_DISCONNECTED) + mark_as_advanced(FETCHCONTENT_UPDATES_DISCONNECTED) + mark_as_advanced(FETCHCONTENT_QUIET) + list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/../cmake) + include(SoloCustomMacros) +else() + set(THIRDPARTY_BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/../thirdparty) +endif() + +if(NOT DEFINED SOLO_BUILD_DEPS) + # [option] SOLO_BUILD_DEPS (default: ON) + # Download and build missing dependencies, throw error if disabled. + set(SOLO_BUILD_DEPS_OPTION_STR "Automatically download and build unmet dependencies") + option(SOLO_BUILD_DEPS ${SOLO_BUILD_DEPS_OPTION_STR} ON) +endif() + +# if SOLO_BUILD_TEST is ON, use GoogleTest +if(SOLO_BUILD_TEST) + find_package(GTest 1 CONFIG) + if(GTest_FOUND) + message(STATUS "GoogleTest: found") + else() + if(SOLO_BUILD_DEPS) + message(STATUS "GoogleTest: download ...") + solo_fetch_thirdparty_content(ExternalGTest) + add_library(GTest::gtest ALIAS gtest) + else() + message(FATAL_ERROR "GoogleTest: not found, please download and install manually") + endif() + endif() + + # Add source files to test + set(SOLO_TEST_FILES + ${CMAKE_CURRENT_LIST_DIR}/ec_openssl_test.cpp + ${CMAKE_CURRENT_LIST_DIR}/hash_test.cpp + ${CMAKE_CURRENT_LIST_DIR}/prng_test.cpp + ${CMAKE_CURRENT_LIST_DIR}/sampling_test.cpp + ${CMAKE_CURRENT_LIST_DIR}/test_runner.cpp + ) + + set(CMAKE_CXX_LINK_EXECUTABLE "${CMAKE_CXX_LINK_EXECUTABLE} -ldl -lrt") + + add_executable(solo_test ${SOLO_TEST_FILES}) + + if(TARGET PETAce-Solo::solo) + target_link_libraries(solo_test PRIVATE PETAce-Solo::solo GTest::gtest) + elseif(TARGET PETAce-Solo::solo_shared) + target_link_libraries(solo_test PRIVATE PETAce-Solo::solo_shared GTest::gtest) + else() + message(FATAL_ERROR "Cannot find target PETAce-Solo::solo or PETAce-Solo::solo_shared") + endif() + + # In Debug mode, enable AddressSanitizer (and LeakSanitizer) on Unix-like platforms. + if(SOLO_DEBUG AND UNIX) + # On macOS, only AddressSanitizer is enabled. + # On Linux, LeakSanitizer is enabled by default. + target_compile_options(solo_test PUBLIC -fsanitize=address) + target_link_options(solo_test PUBLIC -fsanitize=address) + if(NOT APPLE) + message(STATUS "Sanitizers enabled: address, leak") + else() + message(STATUS "Sanitizers enabled: address") + endif() + endif() + + add_custom_target(test_report + COMMAND ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/solo_test --gtest_output="xml:${CMAKE_CURRENT_BINARY_DIR}/../report/solo_test.xml" + DEPENDS solo_test) +endif() diff --git a/test/ec_openssl_test.cpp b/test/ec_openssl_test.cpp new file mode 100644 index 0000000..9071a61 --- /dev/null +++ b/test/ec_openssl_test.cpp @@ -0,0 +1,65 @@ +// Copyright 2023 TikTok Pte. Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "solo/ec_openssl.h" + +#include "gtest/gtest.h" + +TEST(ECOpenSSLTest, ECScheme) { + petace::solo::Byte input[64]; + for (std::size_t i = 0; i < 64; i++) { + input[i] = static_cast(i); + } + { + using EC = petace::solo::ECOpenSSL; + int curve_id = 415; + EC ec(curve_id, petace::solo::HashScheme::SHA_256); + EC::SecretKey sk; + EC::SecretKey sk_new; + EC::Point plaintext(ec); + EC::Point plaintext_new(ec); + EC::Point ciphertext(ec); + EC::Point ciphertext_new(ec); + petace::solo::PRNGFactory prng_factory(petace::solo::PRNGScheme::BLAKE2Xb); + std::shared_ptr prng = prng_factory.create(); + ASSERT_THROW(ec.create_secret_key(nullptr, sk), std::invalid_argument); + ec.create_secret_key(prng, sk); + ec.create_secret_key(prng, sk_new); + ASSERT_THROW(ec.hash_to_curve(nullptr, 64, plaintext), std::invalid_argument); + ec.hash_to_curve(input, 64, plaintext); + ec.encrypt(plaintext, sk, ciphertext); + ec.switch_key(ciphertext, sk, sk_new, ciphertext_new); + ec.decrypt(ciphertext_new, sk_new, plaintext_new); + ASSERT_TRUE(ec.are_equal(plaintext_new, plaintext)); + + EC::Point pk(ec); + EC::Point pk_other(ec); + EC::UInt scalar; + EXPECT_NO_THROW(BN_copy(scalar.data(), sk.data())); + ec.create_public_key(sk, pk); + ASSERT_FALSE(ec.are_equal(pk, pk_other)); + ec.mul_generator(scalar, pk_other); + ASSERT_TRUE(ec.are_equal(pk, pk_other)); + + std::size_t pk_byte_count = ec.point_to_bytes(pk, 0, nullptr); + std::vector pk_byte_array(pk_byte_count); + ASSERT_EQ(ec.point_to_bytes(pk, pk_byte_count, pk_byte_array.data()), pk_byte_count); + ASSERT_THROW(ec.point_from_bytes(pk_byte_array.data(), 0, pk_other), std::invalid_argument); + ASSERT_THROW(ec.point_from_bytes(nullptr, pk_byte_count, pk_other), std::invalid_argument); + ASSERT_THROW(ec.point_from_bytes(pk_byte_array.data(), pk_byte_count + 1, pk_other), std::runtime_error); + ASSERT_THROW(ec.point_from_bytes(pk_byte_array.data(), pk_byte_count - 1, pk_other), std::runtime_error); + ec.point_from_bytes(pk_byte_array.data(), pk_byte_count, pk_other); + ASSERT_TRUE(ec.are_equal(pk, pk_other)); + } +} diff --git a/test/hash_test.cpp b/test/hash_test.cpp new file mode 100644 index 0000000..2a11020 --- /dev/null +++ b/test/hash_test.cpp @@ -0,0 +1,75 @@ +// Copyright 2023 TikTok Pte. Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "solo/hash.h" + +#include "gtest/gtest.h" + +TEST(HashTest, Hash) { + petace::solo::Byte input[64]; + for (std::size_t i = 0; i < 64; i++) { + input[i] = static_cast(i); + } + { + auto hash = petace::solo::Hash::create(petace::solo::HashScheme::SHA_256); + std::vector block(hash->hash_byte_count()); + EXPECT_NO_THROW(hash->compute(nullptr, 0, nullptr, 0)); + ASSERT_THROW(hash->compute(nullptr, 64, block.data(), block.size()), std::invalid_argument); + ASSERT_THROW(hash->compute(input, 64, nullptr, block.size()), std::invalid_argument); + ASSERT_THROW(hash->compute(input, 64, std::vector(hash->hash_byte_count() + 1).data(), + hash->hash_byte_count() + 1), + std::invalid_argument); + hash->compute(input, 64, block.data(), block.size()); + std::vector check(hash->hash_byte_count()); + hash->compute(input, 64, check.data(), check.size()); + ASSERT_EQ(block, check); + ASSERT_NE(petace::solo::HashSHA_256::create(), nullptr); + petace::solo::HashFactorySHA_256 hash_factory; + ASSERT_NE(hash_factory.create(), nullptr); + } + { + auto hash = petace::solo::Hash::create(petace::solo::HashScheme::SHA3_256); + std::vector block(hash->hash_byte_count()); + EXPECT_NO_THROW(hash->compute(nullptr, 0, nullptr, 0)); + ASSERT_THROW(hash->compute(nullptr, 64, block.data(), block.size()), std::invalid_argument); + ASSERT_THROW(hash->compute(input, 64, nullptr, block.size()), std::invalid_argument); + ASSERT_THROW(hash->compute(input, 64, std::vector(hash->hash_byte_count() + 1).data(), + hash->hash_byte_count() + 1), + std::invalid_argument); + hash->compute(input, 64, block.data(), block.size()); + std::vector check(hash->hash_byte_count()); + hash->compute(input, 64, check.data(), check.size()); + ASSERT_EQ(block, check); + ASSERT_NE(petace::solo::HashSHA3_256::create(), nullptr); + petace::solo::HashFactorySHA3_256 hash_factory; + ASSERT_NE(hash_factory.create(), nullptr); + } + { + auto hash = petace::solo::Hash::create(petace::solo::HashScheme::BLAKE2b); + std::vector block(hash->hash_byte_count()); + EXPECT_NO_THROW(hash->compute(nullptr, 0, nullptr, 0)); + ASSERT_THROW(hash->compute(nullptr, 64, block.data(), block.size()), std::invalid_argument); + ASSERT_THROW(hash->compute(input, 64, nullptr, block.size()), std::invalid_argument); + ASSERT_THROW(hash->compute(input, 64, std::vector(hash->hash_byte_count() + 1).data(), + hash->hash_byte_count() + 1), + std::invalid_argument); + hash->compute(input, 64, block.data(), block.size()); + std::vector check(hash->hash_byte_count()); + hash->compute(input, 64, check.data(), check.size()); + ASSERT_EQ(block, check); + ASSERT_NE(petace::solo::HashBLAKE2b::create(), nullptr); + petace::solo::HashFactoryBLAKE2b hash_factory; + ASSERT_NE(hash_factory.create(), nullptr); + } +} diff --git a/test/prng_test.cpp b/test/prng_test.cpp new file mode 100644 index 0000000..3a40402 --- /dev/null +++ b/test/prng_test.cpp @@ -0,0 +1,161 @@ +// Copyright 2023 TikTok Pte. Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "solo/prng.h" + +#include "gtest/gtest.h" + +TEST(PRNGTest, PRNG) { + std::size_t seed_byte_count = 16; + std::vector seed(seed_byte_count, petace::solo::Byte(7)); + { + EXPECT_NO_THROW(petace::solo::PRNG::get_random_byte_array(0, nullptr)); + ASSERT_THROW(petace::solo::PRNG::get_random_byte_array(seed_byte_count, nullptr), std::invalid_argument); + EXPECT_NO_THROW(petace::solo::PRNG::get_random_byte_array( + seed_byte_count, std::vector(seed_byte_count).data())); + EXPECT_NO_THROW(petace::solo::PRNG::get_random_byte_array( + seed_byte_count + 1, std::vector(seed_byte_count + 1).data())); + } + { + ASSERT_THROW(petace::solo::PRNGImpl(std::size_t(0)), std::invalid_argument); + ASSERT_THROW(petace::solo::PRNGImpl(seed_byte_count, std::size_t(0)), + std::invalid_argument); + ASSERT_THROW(petace::solo::PRNGImpl( + std::vector(std::size_t(0)), std::size_t(0)), + std::invalid_argument); + ASSERT_THROW(petace::solo::PRNGImpl(seed, std::size_t(0)), + std::invalid_argument); + } + { + petace::solo::PRNGFactory prng_factory(petace::solo::PRNGScheme::BLAKE2Xb, seed_byte_count); + ASSERT_THROW(auto test_prng = prng_factory.create(std::size_t(0)), std::invalid_argument); + EXPECT_NO_THROW(auto test_prng = prng_factory.create()); + std::shared_ptr prng = prng_factory.create(seed); + std::vector output(8); + ASSERT_THROW(prng->generate(output.size(), nullptr), std::invalid_argument); + prng->generate(output.size(), output.data()); + + std::shared_ptr prng_other = prng_factory.create(seed); + std::vector output_other(8); + prng_other->generate(output_other.size(), output_other.data()); + + for (std::size_t i = 0; i < 8; i++) { + ASSERT_EQ(output[i], output_other[i]); + } + } + { + ASSERT_THROW(petace::solo::PRNGFactory(petace::solo::PRNGScheme::BLAKE2Xb, 0), std::invalid_argument); + petace::solo::PRNGFactory prng_factory(petace::solo::PRNGScheme::BLAKE2Xb, seed_byte_count + 1); + ASSERT_THROW(std::shared_ptr prng = prng_factory.create(seed), std::invalid_argument); + } +#ifdef SOLO_DEBUG + { + ASSERT_THROW(petace::solo::PRNGFactory prng_factory( + petace::solo::PRNGScheme::BLAKE2Xb, std::vector(std::size_t(0))), + std::invalid_argument); + petace::solo::PRNGFactory prng_factory(petace::solo::PRNGScheme::BLAKE2Xb, seed); + auto prng = prng_factory.create(); + ASSERT_EQ(prng->seed(), seed); + } +#endif + { + ASSERT_THROW( + petace::solo::PRNGImpl(std::size_t(0)), std::invalid_argument); + ASSERT_THROW(petace::solo::PRNGImpl(seed_byte_count, std::size_t(0)), + std::invalid_argument); + ASSERT_THROW(petace::solo::PRNGImpl( + std::vector(std::size_t(0)), std::size_t(0)), + std::invalid_argument); + ASSERT_THROW(petace::solo::PRNGImpl(seed, std::size_t(0)), + std::invalid_argument); + } + { + petace::solo::PRNGFactory prng_factory(petace::solo::PRNGScheme::SHAKE_128, seed_byte_count); + ASSERT_THROW(auto test_prng = prng_factory.create(std::size_t(0)), std::invalid_argument); + EXPECT_NO_THROW(auto test_prng = prng_factory.create()); + std::shared_ptr prng = prng_factory.create(seed); + std::vector output(8); + ASSERT_THROW(prng->generate(output.size(), nullptr), std::invalid_argument); + prng->generate(output.size(), output.data()); + + std::shared_ptr prng_other = prng_factory.create(seed); + std::vector output_other(8); + prng_other->generate(output_other.size(), output_other.data()); + + for (std::size_t i = 0; i < 8; i++) { + ASSERT_EQ(output[i], output_other[i]); + } + } + { + ASSERT_THROW(petace::solo::PRNGFactory(petace::solo::PRNGScheme::SHAKE_128, 0), std::invalid_argument); + petace::solo::PRNGFactory prng_factory(petace::solo::PRNGScheme::SHAKE_128, seed_byte_count + 1); + ASSERT_THROW(std::shared_ptr prng = prng_factory.create(seed), std::invalid_argument); + } +#ifdef SOLO_DEBUG + { + ASSERT_THROW(petace::solo::PRNGFactory prng_factory( + petace::solo::PRNGScheme::SHAKE_128, std::vector(std::size_t(0))), + std::invalid_argument); + petace::solo::PRNGFactory prng_factory(petace::solo::PRNGScheme::SHAKE_128, seed); + auto prng = prng_factory.create(); + ASSERT_EQ(prng->seed(), seed); + } +#endif + { + ASSERT_THROW( + petace::solo::PRNGImpl(std::size_t(0)), std::invalid_argument); + ASSERT_THROW(petace::solo::PRNGImpl(seed_byte_count, std::size_t(0)), + std::invalid_argument); + ASSERT_THROW(petace::solo::PRNGImpl( + std::vector(std::size_t(0)), std::size_t(0)), + std::invalid_argument); + ASSERT_THROW(petace::solo::PRNGImpl(seed, std::size_t(0)), + std::invalid_argument); + } + { + petace::solo::PRNGFactory prng_factory(petace::solo::PRNGScheme::AES_ECB_CTR, seed_byte_count); + ASSERT_THROW(auto test_prng = prng_factory.create(std::size_t(0)), std::invalid_argument); + EXPECT_NO_THROW(auto test_prng = prng_factory.create()); + std::shared_ptr prng = prng_factory.create(seed); + std::vector output(8); + ASSERT_THROW(prng->generate(output.size(), nullptr), std::invalid_argument); + prng->generate(output.size(), output.data()); + + std::shared_ptr prng_other = prng_factory.create(seed); + std::vector output_other(8); + prng_other->generate(output_other.size(), output_other.data()); + + for (std::size_t i = 0; i < 8; i++) { + ASSERT_EQ(output[i], output_other[i]); + } + } + { + ASSERT_THROW(petace::solo::PRNGFactory(petace::solo::PRNGScheme::AES_ECB_CTR, 0), std::invalid_argument); + ASSERT_THROW(petace::solo::PRNGFactory prng_factory(petace::solo::PRNGScheme::AES_ECB_CTR, seed_byte_count + 1), + std::invalid_argument); + } +#ifdef SOLO_DEBUG + { + ASSERT_THROW(petace::solo::PRNGFactory prng_factory( + petace::solo::PRNGScheme::AES_ECB_CTR, std::vector(std::size_t(0))), + std::invalid_argument); + ASSERT_THROW(petace::solo::PRNGFactory prng_factory(petace::solo::PRNGScheme::AES_ECB_CTR, + std::vector(std::size_t(seed_byte_count + 1))), + std::invalid_argument); + petace::solo::PRNGFactory prng_factory(petace::solo::PRNGScheme::AES_ECB_CTR, seed); + auto prng = prng_factory.create(); + ASSERT_EQ(prng->seed(), seed); + } +#endif +} diff --git a/test/sampling_test.cpp b/test/sampling_test.cpp new file mode 100644 index 0000000..6dbabce --- /dev/null +++ b/test/sampling_test.cpp @@ -0,0 +1,50 @@ +// Copyright 2023 TikTok Pte. Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "solo/sampling.h" + +#include "gtest/gtest.h" + +#include "solo/prng.h" + +TEST(SamplingTest, Uniform) { + std::size_t seed_byte_count = 64; + std::vector seed(seed_byte_count, petace::solo::Byte(7)); + petace::solo::PRNGFactory prng_factory(petace::solo::PRNGScheme::SHAKE_128, seed_byte_count); + std::shared_ptr prng = prng_factory.create(seed); + { + petace::solo::Byte rand_byte = petace::solo::sample_uniform_byte(*prng); + ASSERT_EQ(rand_byte, petace::solo::Byte(0x4F)); + std::uint32_t rand_uint32 = petace::solo::sample_uniform_uint32(*prng); + ASSERT_EQ(rand_uint32, std::uint32_t(4026420270)); + std::uint64_t rand_uint64 = petace::solo::sample_uniform_uint64(*prng); + ASSERT_EQ(rand_uint64, std::uint64_t(3930362238982664675)); + } + { + petace::solo::Byte rand_byte[1]; + petace::solo::sample_uniform_byte_array(*prng, std::size_t(1), rand_byte); + ASSERT_EQ(rand_byte[0], petace::solo::Byte(0x0B)); + std::uint32_t rand_uint32[1]; + petace::solo::sample_uniform_uint32_array(*prng, std::size_t(1), rand_uint32); + ASSERT_EQ(rand_uint32[0], std::uint32_t(2044260689)); + std::uint64_t rand_uint64[1]; + petace::solo::sample_uniform_uint64_array(*prng, std::size_t(1), rand_uint64); + ASSERT_EQ(rand_uint64[0], std::uint64_t(16049053167049065388ULL)); + } + { + ASSERT_THROW(petace::solo::sample_uniform_byte_array(*prng, std::size_t(1), nullptr), std::invalid_argument); + ASSERT_THROW(petace::solo::sample_uniform_uint32_array(*prng, std::size_t(1), nullptr), std::invalid_argument); + ASSERT_THROW(petace::solo::sample_uniform_uint64_array(*prng, std::size_t(1), nullptr), std::invalid_argument); + } +} diff --git a/test/test_runner.cpp b/test/test_runner.cpp new file mode 100644 index 0000000..288bbd2 --- /dev/null +++ b/test/test_runner.cpp @@ -0,0 +1,23 @@ +// Copyright 2023 TikTok Pte. Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "gtest/gtest.h" + +/** +Main entry point for Google Test unit tests. +*/ +int main(int argc, char** argv) { + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/tools/clang-format-all.sh b/tools/clang-format-all.sh new file mode 100644 index 0000000..48b1271 --- /dev/null +++ b/tools/clang-format-all.sh @@ -0,0 +1,27 @@ +#!/bin/bash + +# Copyright 2023 TikTok Pte. Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +BASE_DIR=$(dirname "$0") +ROOT_DIR=$BASE_DIR/../ +shopt -s globstar +clang-format -i $ROOT_DIR/bench/**/*.h +clang-format -i $ROOT_DIR/bench/**/*.cpp +clang-format -i $ROOT_DIR/example/**/*.h +clang-format -i $ROOT_DIR/example/**/*.cpp +clang-format -i $ROOT_DIR/src/**/*.h +clang-format -i $ROOT_DIR/src/**/*.cpp +clang-format -i $ROOT_DIR/test/**/*.h +clang-format -i $ROOT_DIR/test/**/*.cpp