Last active
September 26, 2021 20:22
-
-
Save Sam-Belliveau/1ebc71da8c841e0ea0b9bdd308cf0fa1 to your computer and use it in GitHub Desktop.
Fast, Secure, Robust, and Documented Implementation of ChaCha for C++. Compatible with the <random> header file and interfaces
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* MIT License | |
* | |
* Copyright (c) 2021, Samuel Robert Belliveau | |
* | |
* Permission is hereby granted, free of charge, to any person obtaining a copy | |
* of this software and associated documentation files (the "Software"), to deal | |
* in the Software without restriction, including without limitation the rights | |
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
* copies of the Software, and to permit persons to whom the Software is | |
* furnished to do so, subject to the following conditions: | |
* | |
* The above copyright notice and this permission notice shall be included in all | |
* copies or substantial portions of the Software. | |
* | |
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
* SOFTWARE. | |
*/ | |
#ifndef SAM_BELLIVEAU_CHA_CHA_LIBRARY | |
#define SAM_BELLIVEAU_CHA_CHA_LIBRARY 1 // Just incase somebody wants to use it like a bool | |
#include <algorithm> // std::copy - copy arrays | |
#include <cstdint> // std::uint8_t, std::uint32_t, std::uint64_t, std::size_t | |
#include <cstdlib> // std::time(), std::rand() | |
#include <limits> // std::numeric_limits - min/max | |
#include <array> // std::array - used for passing and storing sequences | |
#include <random> // std::random_device - Used for collecting entropy | |
#include <chrono> // std::chrono::high_resolution_clock - Used for collecting entropy | |
namespace Cha | |
{ | |
// Types used throughout code | |
namespace Type | |
{ | |
// Base Types | |
using Byte = std::uint8_t; | |
using Word = std::uint32_t; | |
using DWord = std::uint64_t; | |
using Size = std::size_t; | |
// State Type | |
using State = std::array<Word, 16>; | |
// Input To State Types | |
using Key = std::array<Word, 8>; | |
using Nonce = DWord; | |
using Constant = std::array<Word, 4>; | |
using Position = DWord; | |
using Counter = Size; | |
// Check If The Input Types Add Up To The State Size | |
static_assert(sizeof(State) == sizeof(Key) + sizeof(Nonce) + sizeof(Constant) + sizeof(Position), | |
"Error With Size of Variables -> does not sum to Size of State"); | |
} | |
// Default Values for the ChaCha engine | |
namespace Default { | |
// SHA256 Hash of string "ChaCha" as default key | |
static constexpr Type::Key Key = { | |
0xde68d987, 0x5e9fd141, 0x16a1a3bf, 0xedcbdcfe, | |
0x99f95736, 0xc7455d65, 0x485c2700, 0x48b98907 | |
}; | |
// Random nonce number | |
static constexpr Type::Nonce Nonce = 0xDEADBEEFDEADBEEF; | |
// "expand 32-byte k" | |
static constexpr Type::Constant Constant = {0x65787061, 0x6e642033, 0x322d6279, 0x7465206b}; | |
// Starting position counter at 0 | |
static constexpr Type::Position Position = 0; | |
// Starting counter at 0 | |
static constexpr Type::Counter Counter = 0; | |
} | |
// Generate High Entropy values for Seeding | |
namespace Generate { | |
static constexpr Type::Size DefaultRounds = 64; | |
// Generate High Entropy Base Types | |
namespace Raw { | |
/** | |
* Slow, Secure way to generate a maximum entropy | |
* 64 bit integer. Used for seeding. | |
*/ | |
static Type::DWord DWord(Type::Size rounds = DefaultRounds) | |
{ | |
// We can use random_device as an entropy source. Even if it isn't | |
// cryptographically secure, the mixing process will make it secure | |
std::random_device entropy_device; | |
// Pool where all the entropy is stored | |
Type::DWord entropy_pool = static_cast<Type::DWord>(std::time(NULL)); | |
// Rotate entropy pool around while xor-ing entropy into it | |
for(Type::Size i = 0; i < rounds; ++i) { | |
// Rotate entropy_pool left by 23 bits | |
entropy_pool = (entropy_pool << 23) | (entropy_pool >> 41); | |
entropy_pool ^= static_cast<Type::DWord>(entropy_device()); | |
entropy_pool ^= static_cast<Type::DWord>(std::chrono::high_resolution_clock::now().time_since_epoch().count()); | |
entropy_pool ^= static_cast<Type::DWord>(std::rand()); | |
} | |
return entropy_pool; | |
} | |
/** | |
* Slow, Secure way to generate a maximum entropy | |
* 32 bit integer. Used for seeding. | |
*/ | |
static Type::Word Word(Type::Size rounds = DefaultRounds) | |
{ | |
Type::DWord result = DWord(rounds); | |
result ^= result >> 32; | |
return static_cast<Type::Word>(result & 0xffffffff); | |
} | |
} | |
/** | |
* Slow, Secure way to generate a maximum entropy | |
* Nonce value. Used for seeding. | |
*/ | |
static Type::Nonce Nonce(Type::Size rounds = DefaultRounds) | |
{ return Raw::DWord(rounds); } | |
/** | |
* Slow, Secure way to generate a maximum entropy | |
* Key value. Used for seeding. | |
*/ | |
static Type::Key Key(Type::Size rounds = DefaultRounds) | |
{ | |
Type::Key output; | |
for(auto& i : output) i = Raw::Word(rounds); | |
return output; | |
} | |
} | |
// Class that does the randomness stuff | |
template<Type::Size Rounds> | |
class ChaCha | |
{ | |
public: | |
/** | |
* Creates a high quality seeded engine using | |
* the functions found in generate | |
*/ | |
static ChaCha RandomEngine(Type::Size rounds = Generate::DefaultRounds) noexcept | |
{ return ChaCha(Generate::Key(rounds), Generate::Nonce(rounds)); } | |
private: // State Variables | |
Type::State state_; | |
Type::Position pos_; | |
Type::Counter ctr_; | |
Type::Nonce nonce_; | |
public: // Constructors | |
/** | |
* Seeds the engine by setting up every | |
* part of the state. Refer to | |
* https://en.wikipedia.org/wiki/Salsa20#ChaCha_variant | |
* for more information | |
*/ | |
inline constexpr ChaCha( | |
const Type::Key& key = Default::Key, | |
const Type::Nonce nonce = Default::Nonce) noexcept | |
{ seed(key, nonce); } | |
/** | |
* Seeds and Resets the engine with a 64bit | |
* unsigned integer by using it as the nonce value | |
*/ | |
inline constexpr ChaCha(const Type::Nonce nonce) noexcept { seed(nonce); } | |
/** | |
* Default Copy Constructors | |
*/ | |
inline constexpr ChaCha(const ChaCha& other) noexcept = default; | |
inline constexpr ChaCha& operator=(const ChaCha& other) = default; | |
public: // Comparison Operators | |
template<Type::Size R> | |
inline constexpr bool operator==(const ChaCha<R>& other) const noexcept | |
{ | |
return (state_ == other.state_) | |
&& (pos_ == other.pos_) | |
&& (nonce_ == other.nonce_) | |
&& (Rounds == R); | |
} | |
template<Type::Size R> | |
inline constexpr bool operator!=(const ChaCha<R>& other) const noexcept | |
{ return !(operator==(other)); } | |
private: // Setting Up State (Private) | |
/** | |
* Move constant variable into | |
* the state and its positions | |
*/ | |
inline constexpr void set_state_constant(const Type::Constant& constant) | |
{ | |
// Copy Constant into slots 0 - 3 | |
state_[0x0] = constant[0x0]; state_[0x1] = constant[0x1]; | |
state_[0x2] = constant[0x2]; state_[0x3] = constant[0x3]; | |
} | |
/** | |
* Move key variable into the | |
* state and its positions | |
*/ | |
inline constexpr void set_state_key(const Type::Key& key) | |
{ | |
// Copy Key into slots 4 - 11 | |
state_[0x4] = key[0x0]; state_[0x5] = key[0x1]; | |
state_[0x6] = key[0x2]; state_[0x7] = key[0x3]; | |
state_[0x8] = key[0x4]; state_[0x9] = key[0x5]; | |
state_[0xA] = key[0x6]; state_[0xB] = key[0x7]; | |
} | |
/** | |
* Put a 64 bit word into two | |
* 32 bit array indexes | |
*/ | |
template<Type::Size Position> | |
inline constexpr void set_state_dword(const Type::DWord word) noexcept | |
{ | |
state_[Position + 0] = (word >> 32) & 0xffffffff; | |
state_[Position + 1] = (word >> 0) & 0xffffffff; | |
} | |
/** | |
* This function just has the position of | |
* the position variable built in | |
*/ | |
inline constexpr void set_state_pos(const Type::Position pos) noexcept | |
{ set_state_dword<12>(pos); } | |
/** | |
* This function just has the position of | |
* the nonce variable built in | |
*/ | |
inline constexpr void set_state_nonce(const Type::Nonce nonce) noexcept | |
{ set_state_dword<14>(nonce); } | |
public: // Setting Up State (Public) | |
/** | |
* Seeds and Resets the engine by setting | |
* up every part of the state. Refer to | |
* https://en.wikipedia.org/wiki/Salsa20#ChaCha_variant | |
* for more information | |
*/ | |
inline constexpr void seed( | |
const Type::Key& key = Default::Key, | |
const Type::Nonce nonce = Default::Nonce | |
) noexcept { | |
// Set the Nonce variable and keep track of it | |
nonce_ = nonce; | |
// Set Position to default start | |
pos_ = Default::Position; | |
// Set Counter to default start | |
ctr_ = Default::Counter; | |
// Move Variables into state | |
set_state_constant(Default::Constant); | |
set_state_key(key); | |
set_state_nonce(nonce_); | |
set_state_pos(pos_); | |
} | |
/** | |
* Seeds and Resets the engine with a 64bit | |
* unsigned integer by using it as the nonce value. | |
*/ | |
inline constexpr void seed(const Type::DWord nonce) noexcept | |
{ seed(Default::Key, nonce); } | |
private: // Round Helpers | |
/** | |
* Rotate 32 bit integer constexpr amount | |
* Helps with compiler optimization | |
*/ | |
template<Type::Size Bits> | |
static inline constexpr Type::Word rotatel(const Type::Word i) noexcept | |
{ | |
constexpr int Reverse = (sizeof(Type::Word) * 8) - Bits; | |
return (i << Bits) | (i >> Reverse); | |
} | |
/** | |
* Mix 4 words in quarter round | |
*/ | |
static inline constexpr void quarter_round( | |
Type::Word& a, Type::Word& b, Type::Word& c, Type::Word& d) noexcept | |
{ | |
a += b; d = rotatel<16>(d ^ a); c += d; b = rotatel<12>(b ^ c); | |
a += b; d = rotatel< 8>(d ^ a); c += d; b = rotatel< 7>(b ^ c); | |
} | |
/** | |
* Mix state by adding a mixed version | |
* of the state to the origional | |
*/ | |
inline constexpr void mix_state() noexcept | |
{ | |
// Copy state from before mixing | |
Type::State mix = state_; | |
// Mix state using odd/even rounds | |
for(Type::Size i = 0; i < Rounds; i += 2) | |
{ | |
// Odd Round | |
quarter_round(mix[0], mix[4], mix[ 8], mix[12]); | |
quarter_round(mix[1], mix[5], mix[ 9], mix[13]); | |
quarter_round(mix[2], mix[6], mix[10], mix[14]); | |
quarter_round(mix[3], mix[7], mix[11], mix[15]); | |
// Even Round | |
quarter_round(mix[0], mix[5], mix[10], mix[15]); | |
quarter_round(mix[1], mix[6], mix[11], mix[12]); | |
quarter_round(mix[2], mix[7], mix[ 8], mix[13]); | |
quarter_round(mix[3], mix[4], mix[ 9], mix[14]); | |
} | |
// You have to add the mixed state to the original, | |
// or else it would be easy to reverse the mixing | |
for(Type::Size i = 0; i < mix.size(); ++i) | |
{ state_[i] += mix[i]; } | |
} | |
/** | |
* Iterate state of engine specified number of rounds | |
*/ | |
inline constexpr void iterate(void) noexcept | |
{ | |
// Put Position Data In State | |
set_state_pos(pos_++); | |
// Mix state | |
mix_state(); | |
} | |
public: // Standard Random Interface (Information) | |
/** | |
* Type that random engine will return | |
*/ | |
using result_type = Type::Word; | |
/** | |
* Returns the smallest value the engine will return. | |
*/ | |
static constexpr result_type min(void) | |
{ return std::numeric_limits<result_type>::min(); } | |
/** | |
* Returns the largest value the engine will return. | |
*/ | |
static constexpr result_type max(void) | |
{ return std::numeric_limits<result_type>::max(); } | |
public: // Standard Random Interface (Generation) | |
/** | |
* Gets the next result from the rng engine. | |
* Will iterate the state every 8 rounds. | |
* Does not return parts of state where key is given. | |
*/ | |
inline constexpr result_type get(void) noexcept { | |
switch((ctr_++) & 0x7) { | |
case 0x0: iterate(); | |
return state_[0x0]; break; | |
case 0x1: return state_[0x1]; break; | |
case 0x2: return state_[0x2]; break; | |
case 0x3: return state_[0x3]; break; | |
case 0x4: return state_[0xC]; break; | |
case 0x5: return state_[0xD]; break; | |
case 0x6: return state_[0xE]; break; | |
case 0x7: return state_[0xF]; break; | |
default: return -1; break; | |
} | |
} | |
/** | |
* Calls get() and returns value | |
*/ | |
inline constexpr result_type operator()(void) noexcept | |
{ return get(); } | |
/** | |
* Skip ahead a specified amount ahead in the engine. | |
* Equivilant to calling operator(), but faster. | |
*/ | |
inline constexpr void discard(Type::Size delta = 1) noexcept | |
{ | |
do { get(); } | |
while(--delta); | |
} | |
public: // Get Information about RNG | |
/** | |
* Returns a constant referance to an std::array of | |
* 32 bit integers of size 16. | |
*/ | |
inline constexpr const auto& state(void) const noexcept { return state_; } | |
/** | |
* Returns nonce used by engine | |
* | |
* This will never change. This lets | |
* you get random nonce value after | |
* initialization | |
*/ | |
inline constexpr auto nonce(void) const noexcept { return nonce_; } | |
/** | |
* Returns the position of the engine. | |
* | |
* The position increments everytime | |
* the state iterates. Usually 1/8th | |
* as much as the counter variable. | |
*/ | |
inline constexpr auto position(void) const noexcept { return pos_; } | |
/** | |
* Returns counter variable of state. | |
* | |
* The counter increments everytime | |
* get() or operator() is called. | |
*/ | |
inline constexpr auto counter(void) const noexcept { return ctr_; } | |
}; | |
// All of these settings pass the die hard test | |
using ChaCha4 = ChaCha<4>; // Recommended for general purpose RNG | |
using ChaCha12 = ChaCha<12>; // Recommended Setting for Cyptography | |
using ChaCha20 = ChaCha<20>; // Default Setting for ChaCha | |
} | |
#endif |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment