diff --git a/.gitignore b/.gitignore index 64e3886..5f760d9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,7 @@ +test_data +TEST* +*.rc +*.rc.dec rain/**/* rainsum .*.swp diff --git a/Makefile b/Makefile index 9f530cf..11570d6 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ CXX = clang++ CXXFLAGS = -std=c++17 -Wall -Wextra -pedantic -O3 CXXFLAGS += -isysroot $(shell xcrun --show-sdk-path) -CXXFLAGS += -fuse-ld=ld +CXXFLAGS += -fuse-ld=ld -lz DEPFLAGS = -MMD -MF $(@:.o=.d) LDFLAGS = diff --git a/TODO b/TODO new file mode 100644 index 0000000..0771cee --- /dev/null +++ b/TODO @@ -0,0 +1,7 @@ +cipher improvement + +- compress P +- schedule K into block keys via XOF +- spread indexing and parallelization for cipher mining efficiency + + diff --git a/docs/paper/cipher-note.pdf b/docs/paper/cipher-note.pdf new file mode 100644 index 0000000..c930c1b Binary files /dev/null and b/docs/paper/cipher-note.pdf differ diff --git a/docs/paper/cipher-note.tex b/docs/paper/cipher-note.tex new file mode 100644 index 0000000..b22aa5a --- /dev/null +++ b/docs/paper/cipher-note.tex @@ -0,0 +1,211 @@ +\documentclass[11pt,a4paper]{article} +\usepackage[margin=1in]{geometry} +\usepackage{amsmath,amssymb,amsfonts} +\usepackage{hyperref} +\usepackage{lmodern} +\usepackage[T1]{fontenc} +\usepackage{array} +\usepackage{booktabs} +\usepackage{listings} +\usepackage{xcolor} + +\hypersetup{ + colorlinks=true, + linkcolor=blue, + urlcolor=blue, + citecolor=blue +} + +\title{Digest Mining Ciphers: Novel Hash-Based Pre-Image Security Primitives} +\author{Cris, DOSAYGO} +\date{\today} + +\setlength{\parskip}{6pt} +\setlength{\abovedisplayskip}{10pt} +\setlength{\belowdisplayskip}{10pt} + +\begin{document} +\maketitle + +\section*{Abstract} +This note introduces a novel hash-based encryption scheme named \textbf{Digest Mining Encryption} (DME), which leverages the pre-image resistance and nonlinear properties of cryptographic hash functions. Unlike traditional encryption methods that utilize P-boxes, S-boxes, or nonlinear algebraic transformations, DME relies on mining plaintext blocks from a nonce and key by solving for digest mappings. The scheme supports multiple digest selection modes (prefix, sequence, scatter) and optional compression, offering flexibility and robust resistance against brute-force and timing attacks. Additionally, DME aligns with hash-based post-quantum cryptographic techniques, presenting potential for quantum-resistant asymmetric systems. Challenges such as ciphertext expansion and mining speed are addressed through optimization and compression. A proof-of-concept implementation is provided to facilitate further exploration and analysis by the cryptographic community. + +\section*{Introduction} +Traditional cryptographic primitives, such as block ciphers, achieve security through carefully designed P-boxes (permutation), S-boxes (substitution), and algebraic nonlinear transformations. These components introduce diffusion and confusion, creating ciphertext that resists cryptanalysis. + +In contrast, \textbf{Digest Mining Encryption} (DME) relies solely on the one-way, nonlinear properties of cryptographic hash functions. Encryption involves solving a \textit{digest mining problem}, where plaintext blocks are \textit{mined} from the output of a keyed hash function and a random nonce. This inversion of normal cryptographic principles introduces a new paradigm, where security derives from the difficulty of pre-image search. + +The scheme encrypts plaintext by mapping blocks into keyed hash digests. Each plaintext block is constructed by selecting specific parts of the digest using a mapping function, defined by the chosen encryption mode. The process incorporates optional compression to reduce redundancy and mitigate ciphertext expansion. + +\textbf{Disclaimer:} This document introduces an experimental concept intended for research and analysis by cryptographers. The scheme presented here has not been formally proven secure or subjected to rigorous cryptanalysis. + +\section*{Motivation} +Digest Mining Encryption is inspired by the inherent cryptographic properties of hash functions, which include: + +\subsection*{1. Nonlinearity and One-Wayness} +Cryptographic hash functions are inherently nonlinear and computationally one-way. These properties ensure that: +\begin{itemize} + \item Given a hash output, recovering the input is computationally infeasible (pre-image resistance). + \item Small changes to the input produce unpredictable and significant changes to the output (avalanche effect). +\end{itemize} +By leveraging these properties, DME avoids the need for traditional nonlinear components like S-boxes and instead relies on hash digests as the foundation for security. + +\subsection*{2. Resistance to Timing Attacks} +The encryption process involves stochastic mining of a nonce such that the resulting digest matches the desired mapping. This inherently probabilistic process ensures that: +\begin{itemize} + \item Encryption time varies unpredictably. + \item Fixed execution patterns, which are often exploited in timing attacks, are eliminated. +\end{itemize} + +\subsection*{3. Q-Day Resistance} +Hash functions are well-regarded for their resistance to quantum adversaries: +\begin{itemize} + \item Grover's algorithm reduces brute-force complexity from \( 2^n \) to \( 2^{n/2} \), but sufficiently large hash outputs remain secure. + \item Cryptographic hash functions are widely adopted in post-quantum designs, including hash-based signature schemes like SPHINCS+. +\end{itemize} +DME aligns with this philosophy by building its security on the same primitives, making it inherently resistant to quantum adversaries. + +\subsection*{4. Potential for Integration with Hash-Based Key Exchange} +Hash-based constructions like SPHINCS+ demonstrate the viability of hash functions in asymmetric cryptography. DME could potentially integrate with these schemes, enabling the development of quantum-resistant asymmetric encryption systems that extend the principles of digest mining. + +\section*{Encryption: Core Equation} +The encryption process solves the following equation: +\[ +\text{Map}(H(K \parallel N)) = P +\] +where: +\begin{itemize} + \item \( H \): A cryptographic hash function (e.g., SHA-256, BLAKE3). + \item \( K \): A secret key with sufficient entropy (e.g., 128 bits). + \item \( N \): A per-block random nonce (e.g., 64+ bits; larger nonces increase attack hardness). + \item \( P \): The plaintext block (e.g., 3 bytes, though any length is permitted). + \item \( \text{Map} \): A mapping function selecting parts of the digest \( H(K \parallel N) \) to construct \( P \). +\end{itemize} + +\subsection*{Digest Mining as Encryption} +Plaintext is derived from the digest via: +\[ +P = H(K \parallel N) \otimes \text{Map} +\] +where \( \otimes \) represents the selection operation. Encryption requires mining a nonce \( N \) such that the hash digest satisfies the mapping constraints defined by \( \text{Map} \). + +\subsection*{Ciphertext Encoding} +Each encrypted block is represented as: +\[ +C = (N, \text{indices}) +\] +where \( N \) is the nonce and \( \text{indices} \) defines the mapping used to reconstruct \( P \) from \( H(K \parallel N) \). + +\subsection*{Encryption Modes} +The mapping \( \text{Map} \) can operate in the following modes: +\begin{itemize} + \item \textbf{Prefix Mode:} Matches the first \( n \)-bytes of the digest. + \item \textbf{Sequence Mode:} Matches a contiguous substring of the digest. + \item \textbf{Scatter Mode:} Matches bytes at arbitrary indices in the digest, ensuring no duplicates. +\end{itemize} +Scatter mode ensures that plaintext redundancy does not reveal patterns in the digest indices. + +\section*{Decryption} +Decryption reconstructs the plaintext block \( P \) as: +\[ +P = H(K \parallel N) \otimes \text{indices} +\] +The hash digest is computed with the provided \( N \) and \( K \), and the mapping defined by \( \text{indices} \) extracts the plaintext \( P \). + +\section*{Security Analysis} +\subsection*{Pre-Image Security} +The security of the scheme relies on the pre-image resistance of \( H \). Specifically, given \( P \), \( N \), and \( \text{indices} \), an attacker must solve: +\[ +H(K \parallel N) = P +\] +The effective brute-force effort is: +\[ +2^{|K| + |\text{nonce}|} +\] +where \( |K| \) and \( |\text{nonce}| \) are the bit lengths of the key and nonce, respectively. + +\subsection*{Preventing Redundancy Patterns} +Scatter mode ensures that each index is unique, preventing repeated plaintext bytes from being mapped to the same digest index. This avoids revealing plaintext patterns and ensures uniform utilization of the digest. + +\subsection*{Ciphertext Expansion} +Ciphertext expansion is determined by: +\[ +\text{Overhead} = |\text{nonce}| + |\text{indices}| +\] +Scatter mode incurs higher overhead due to the need to store an index for each byte of \( P \). Optional compression reduces redundancy in the plaintext before encryption, offsetting this overhead. + +\subsection*{Timing Security} +Mining the random nonce \( N \) involves a probabilistic search for a valid mapping \( \text{Map}(H(K \parallel N)) = P \). The stochastic runtime behavior ensures that timing attacks are mitigated, as the encryption time varies unpredictably due to secure randomness in the nonce. + +\section*{Tradeoffs} +\begin{itemize} + \item \textbf{Efficiency vs. Expansion:} Prefix and sequence modes minimize ciphertext size but take longer to mine than the looser matching of scatter mode. Scatter mode is faster to mine but increases expansion. + \item \textbf{Compression:} Reduces plaintext redundancy, often resulting in smaller effective ciphertext size despite normal expansion overhead. + \item \textbf{Mining Speed:} Smaller blocks are faster to mine encrypt, but larger blocks reduce the total number of blocks and nonces. + \item \textbf{Timing Security vs. Constant Time Operations:} While stochastic runtime enhances timing security, it means encryption is probabilistic and does not execute in constant time. +\end{itemize} + +\section*{Tradeoff Summary} + +\begin{center} +\small +\begin{tabular}{@{}p{2in}p{1.8in}p{2.4in}@{}} +\toprule +\textbf{Aspect} & \textbf{Advantage} & \textbf{Limitation} \\ \midrule +Block size & Smaller blocks speed up mining & Larger ciphertext due to nonce overhead \\ +Ciphertext expansion & Mitigated by compression & Compression adds preprocessing complexity \\ +Mining modes & Scatter mode provides faster mining & Requires storing an index for every plaintext byte \\ +Timing security & Stochastic runtime eliminates fixed patterns & Mining is probabilistic and not constant time \\ +\bottomrule +\end{tabular} +\end{center} + +\section*{Proof of Concept} +The repository contains a working proof-of-concept toy implementation demonstrating the feasibility of this hash-based encryption scheme. This toy is not attacked or analyzed and should not be used by third parties (i.e., you!) for securing valuables. The toy cipher does not use established cryptographic hash functions but instead utilizes the hashes defined in this repository, which are high quality and fast hash functions that pass SMHasher3. The code supports all defined digest search modes (prefix, sequence, scatter) and uses a constant key per session, without a key schedule. It also incorporates compression for improved storage efficiency. + +The repository can be found at: \url{https://github.com/DOSAYGO-Research/rain}. + +To build and run the example: +\begin{verbatim} +# Clone the repository +git clone https://github.com/DOSAYGO-Research/rain +cd rain + +# Build using make +make + +# or if encountering problems, try +./scripts/build.sh + +# Example usage +# Encrypt a file with sequence mode +./rain/bin/rainsum -m enc --search-mode sequence input_file.txt + +# Decrypt the file +./rain/bin/rainsum -m dec encrypted_file.rc + +# Verify the decrypted file matches the original +diff input_file.txt decrypted_file.txt +\end{verbatim} + +The repository includes a suite of test scripts to validate the correctness of the implementation across various configurations, ensuring decrypted contents match the originals. These tests systematically evaluate different combinations of hash functions, digest sizes, nonce sizes, block sizes, and input files. The test suite can be executed as follows: +\begin{verbatim} +./scripts/test_cipher.sh +\end{verbatim} + +\section*{Future Work} +Future enhancements include: +\begin{itemize} + \item Introducing a key schedule to vary \( K \) across blocks, improving resistance against related-key attacks, potentially using the hash function in an extendable-output (XOF) mode. + \item Optimizing for parallelized mining to accelerate encryption. + \item Extending compatibility with alternative hash functions for performance tuning. + \item Formalizing security proofs under standard cryptographic assumptions, including resistance to differential and pre-image attacks. + \item Investigating integration with hash-based key exchange schemes like SPHINCS+ to develop quantum-resistant asymmetric encryption systems. + \item Evaluating the impact of different compression algorithms on ciphertext size and encryption speed. +\end{itemize} + +\section*{Conclusion} +\textbf{Digest Mining Encryption} (DME) flips traditional cryptographic principles by leveraging cryptographic hash functions for nonlinear one-way transformations. By relying on pre-image search for plaintext block construction, DME provides robust resistance to brute-force and timing attacks. The scheme’s alignment with hash-based post-quantum techniques offers a foundation for exploring quantum-resistant cryptographic systems. While challenges such as ciphertext expansion and mining speed remain, ongoing improvements aim to balance efficiency and security. This experimental concept invites scrutiny and analysis from the cryptographic community. + +\end{document} + diff --git a/scripts/build.sh b/scripts/build.sh index cfb1838..5657963 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -1,10 +1,14 @@ #!/bin/bash if [ ! -f src/cxxopts.hpp ]; then - curl -L https://raw.githubusercontent.com/jarro2783/cxxopts/eb787304d67ec22f7c3a184ee8b4c481d04357fd/include/cxxopts.hpp -o src/cxxopts.hpp + curl -L https://raw.githubusercontent.com/jarro2783/cxxopts/4bf61f08697b110d9e3991864650a405b3dd515d/include/cxxopts.hpp -o src/cxxopts.hpp fi export PATH="/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin:${PATH}" +cd ../emsdk +source ./emsdk_env.sh + +cd ../rain make diff --git a/src/common.h b/src/common.h index 80e1825..2023d30 100644 --- a/src/common.h +++ b/src/common.h @@ -1,6 +1,6 @@ #pragma once -#define __ENDIAN_H_VERSION__ "1.0.1" +#define __ENDIAN_H_VERSION__ "1.3.0" #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ constexpr bool bswap = false; diff --git a/src/cxxopts.hpp b/src/cxxopts.hpp index b789a5c..0b272ac 100644 --- a/src/cxxopts.hpp +++ b/src/cxxopts.hpp @@ -27,6 +27,7 @@ THE SOFTWARE. #ifndef CXXOPTS_HPP_INCLUDED #define CXXOPTS_HPP_INCLUDED +#include #include #include #include @@ -55,8 +56,8 @@ THE SOFTWARE. #define CXXOPTS_LINKONCE_CONST __declspec(selectany) extern #define CXXOPTS_LINKONCE __declspec(selectany) extern #else -#define CXXOPTS_LINKONCE_CONST -#define CXXOPTS_LINKONCE +#define CXXOPTS_LINKONCE_CONST +#define CXXOPTS_LINKONCE #endif #ifndef CXXOPTS_NO_REGEX @@ -73,6 +74,14 @@ THE SOFTWARE. # endif #endif +#define CXXOPTS_FALLTHROUGH +#ifdef __has_cpp_attribute + #if __has_cpp_attribute(fallthrough) + #undef CXXOPTS_FALLTHROUGH + #define CXXOPTS_FALLTHROUGH [[fallthrough]] + #endif +#endif + #if __cplusplus >= 201603L #define CXXOPTS_NODISCARD [[nodiscard]] #else @@ -84,7 +93,7 @@ THE SOFTWARE. #endif #define CXXOPTS__VERSION_MAJOR 3 -#define CXXOPTS__VERSION_MINOR 1 +#define CXXOPTS__VERSION_MINOR 2 #define CXXOPTS__VERSION_PATCH 1 #if (__GNUC__ < 10 || (__GNUC__ == 10 && __GNUC_MINOR__ < 1)) && __GNUC__ >= 6 @@ -230,10 +239,10 @@ stringAppend(String& s, Iterator begin, Iterator end) } inline -std::size_t +size_t stringLength(const String& s) { - return s.length(); + return static_cast(s.length()); } inline @@ -337,13 +346,8 @@ empty(const std::string& s) namespace cxxopts { namespace { -#ifdef _WIN32 CXXOPTS_LINKONCE_CONST std::string LQUOTE("\'"); CXXOPTS_LINKONCE_CONST std::string RQUOTE("\'"); -#else -CXXOPTS_LINKONCE_CONST std::string LQUOTE("‘"); -CXXOPTS_LINKONCE_CONST std::string RQUOTE("’"); -#endif } // namespace // GNU GCC with -Weffc++ will issue a warning regarding the upcoming class, we @@ -365,6 +369,9 @@ class Value : public std::enable_shared_from_this std::shared_ptr clone() const = 0; + virtual void + add(const std::string& text) const = 0; + virtual void parse(const std::string& text) const = 0; @@ -758,29 +765,31 @@ inline ArguDesc ParseArgument(const char *arg, bool &matched) namespace { CXXOPTS_LINKONCE -std::basic_regex integer_pattern - ("(-)?(0x)?([0-9a-zA-Z]+)|((0x)?0)"); +const char* const integer_pattern = + "(-)?(0x)?([0-9a-zA-Z]+)|((0x)?0)"; CXXOPTS_LINKONCE -std::basic_regex truthy_pattern - ("(t|T)(rue)?|1"); +const char* const truthy_pattern = + "(t|T)(rue)?|1"; CXXOPTS_LINKONCE -std::basic_regex falsy_pattern - ("(f|F)(alse)?|0"); +const char* const falsy_pattern = + "(f|F)(alse)?|0"; CXXOPTS_LINKONCE -std::basic_regex option_matcher - ("--([[:alnum:]][-_[:alnum:]\\.]+)(=(.*))?|-([[:alnum:]].*)"); +const char* const option_pattern = + "--([[:alnum:]][-_[:alnum:]\\.]+)(=(.*))?|-([[:alnum:]].*)"; CXXOPTS_LINKONCE -std::basic_regex option_specifier - ("([[:alnum:]][-_[:alnum:]\\.]*)(,[ ]*[[:alnum:]][-_[:alnum:]]*)*"); +const char* const option_specifier_pattern = + "([[:alnum:]][-_[:alnum:]\\.]*)(,[ ]*[[:alnum:]][-_[:alnum:]]*)*"; CXXOPTS_LINKONCE -std::basic_regex option_specifier_separator(", *"); +const char* const option_specifier_separator_pattern = ", *"; } // namespace inline IntegerDesc SplitInteger(const std::string &text) { + static const std::basic_regex integer_matcher(integer_pattern); + std::smatch match; - std::regex_match(text, match, integer_pattern); + std::regex_match(text, match, integer_matcher); if (match.length() == 0) { @@ -804,15 +813,17 @@ inline IntegerDesc SplitInteger(const std::string &text) inline bool IsTrueText(const std::string &text) { + static const std::basic_regex truthy_matcher(truthy_pattern); std::smatch result; - std::regex_match(text, result, truthy_pattern); + std::regex_match(text, result, truthy_matcher); return !result.empty(); } inline bool IsFalseText(const std::string &text) { + static const std::basic_regex falsy_matcher(falsy_pattern); std::smatch result; - std::regex_match(text, result, falsy_pattern); + std::regex_match(text, result, falsy_matcher); return !result.empty(); } @@ -821,22 +832,25 @@ inline bool IsFalseText(const std::string &text) // (without considering which or how many are single-character) inline OptionNames split_option_names(const std::string &text) { - if (!std::regex_match(text.c_str(), option_specifier)) + static const std::basic_regex option_specifier_matcher(option_specifier_pattern); + if (!std::regex_match(text.c_str(), option_specifier_matcher)) { throw_or_mimic(text); } OptionNames split_names; + static const std::basic_regex option_specifier_separator_matcher(option_specifier_separator_pattern); constexpr int use_non_matches { -1 }; auto token_iterator = std::sregex_token_iterator( - text.begin(), text.end(), option_specifier_separator, use_non_matches); + text.begin(), text.end(), option_specifier_separator_matcher, use_non_matches); std::copy(token_iterator, std::sregex_token_iterator(), std::back_inserter(split_names)); return split_names; } inline ArguDesc ParseArgument(const char *arg, bool &matched) { + static const std::basic_regex option_matcher(option_pattern); std::match_results result; std::regex_match(arg, result, option_matcher); matched = !result.empty(); @@ -959,13 +973,26 @@ integer_parser(const std::string& text, T& value) throw_or_mimic(text); } - const US next = static_cast(result * base + digit); - if (result > next) + US limit = 0; + if (negative) + { + limit = static_cast(std::abs(static_cast(std::numeric_limits::min()))); + } + else + { + limit = std::numeric_limits::max(); + } + + if (base != 0 && result > limit / base) + { + throw_or_mimic(text); + } + if (result * base > limit - digit) { throw_or_mimic(text); } - result = next; + result = static_cast(result * base + digit); } detail::check_signed_range(negative, result, text); @@ -1035,6 +1062,28 @@ parse_value(const std::string& text, T& value) { stringstream_parser(text, value); } +#ifdef CXXOPTS_HAS_OPTIONAL +template +void +parse_value(const std::string& text, std::optional& value) +{ + T result; + parse_value(text, result); + value = std::move(result); +} +#endif + +inline +void parse_value(const std::string& text, char& c) +{ + if (text.length() != 1) + { + throw_or_mimic(text); + } + + c = text[0]; +} + template void parse_value(const std::string& text, std::vector& value) @@ -1054,26 +1103,20 @@ parse_value(const std::string& text, std::vector& value) } } -#ifdef CXXOPTS_HAS_OPTIONAL template void -parse_value(const std::string& text, std::optional& value) +add_value(const std::string& text, T& value) { - T result; - parse_value(text, result); - value = std::move(result); + parse_value(text, value); } -#endif -inline -void parse_value(const std::string& text, char& c) +template +void +add_value(const std::string& text, std::vector& value) { - if (text.length() != 1) - { - throw_or_mimic(text); - } - - c = text[0]; + T v; + add_value(text, v); + value.emplace_back(std::move(v)); } template @@ -1127,6 +1170,12 @@ class abstract_value : public Value m_implicit_value = rhs.m_implicit_value; } + void + add(const std::string& text) const override + { + add_value(text, *m_store); + } + void parse(const std::string& text) const override { @@ -1412,6 +1461,19 @@ struct HelpGroupDetails class OptionValue { public: + void + add + ( + const std::shared_ptr& details, + const std::string& text + ) + { + ensure_value(details); + ++m_count; + m_value->add(text); + m_long_names = &details->long_names(); + } + void parse ( @@ -1498,7 +1560,7 @@ CXXOPTS_DIAGNOSTIC_POP class KeyValue { public: - KeyValue(std::string key_, std::string value_) + KeyValue(std::string key_, std::string value_) noexcept : m_key(std::move(key_)) , m_value(std::move(value_)) { @@ -1551,7 +1613,7 @@ class ParseResult Iterator(const Iterator&) = default; // GCC complains about m_iter not being initialised in the member -// initializer list +// initializer list CXXOPTS_DIAGNOSTIC_PUSH CXXOPTS_IGNORE_WARNING("-Weffc++") Iterator(const ParseResult *pr, bool end=false) @@ -1782,7 +1844,7 @@ class OptionParser ); void - add_to_option(OptionMap::const_iterator iter, const std::string& option, const std::string& arg); + add_to_option(const std::shared_ptr& value, const std::string& arg); void parse_option @@ -1985,6 +2047,7 @@ class Options std::unordered_set m_positional_set{}; //mapping from groups to help options + std::vector m_group{}; std::map m_help{}; }; @@ -2237,6 +2300,7 @@ OptionAdder::operator() case 1: short_name = *first_short_name_iter; option_names.erase(first_short_name_iter); + CXXOPTS_FALLTHROUGH; case 0: break; default: @@ -2328,9 +2392,13 @@ OptionParser::checked_parse_arg inline void -OptionParser::add_to_option(OptionMap::const_iterator iter, const std::string& option, const std::string& arg) +OptionParser::add_to_option(const std::shared_ptr& value, const std::string& arg) { - parse_option(iter->second, option, arg); + auto hash = value->hash(); + auto& result = m_parsed[hash]; + result.add(value, arg); + + m_sequential.emplace_back(value->essential_name(), arg); } inline @@ -2347,14 +2415,14 @@ OptionParser::consume_positional(const std::string& a, PositionalListIterator& n auto& result = m_parsed[iter->second->hash()]; if (result.count() == 0) { - add_to_option(iter, *next, a); + add_to_option(iter->second, a); ++next; return true; } ++next; continue; } - add_to_option(iter, *next, a); + add_to_option(iter->second, a); return true; } throw_or_mimic(*next); @@ -2618,6 +2686,12 @@ Options::add_option } //add the help details + + if (m_help.find(group) == m_help.end()) + { + m_group.push_back(group); + } + auto& options = m_help[group]; options.options.emplace_back(HelpOptionDetails{s, l, stringDesc, @@ -2749,19 +2823,7 @@ inline void Options::generate_all_groups_help(String& result) const { - std::vector all_groups; - - std::transform( - m_help.begin(), - m_help.end(), - std::back_inserter(all_groups), - [] (const std::map::value_type& group) - { - return group.first; - } - ); - - generate_group_help(result, all_groups); + generate_group_help(result, m_group); } inline @@ -2801,19 +2863,7 @@ inline std::vector Options::groups() const { - std::vector g; - - std::transform( - m_help.begin(), - m_help.end(), - std::back_inserter(g), - [] (const std::map::value_type& pair) - { - return pair.first; - } - ); - - return g; + return m_group; } inline diff --git a/src/rainsum.cpp b/src/rainsum.cpp index c28416d..9b05751 100644 --- a/src/rainsum.cpp +++ b/src/rainsum.cpp @@ -1,14 +1,52 @@ #include #include -#include #include #include +#include +#include +#include #include -#include "tool.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "tool.h" // for invokeHash, etc. + + +// ------------------------------------------------------------------ +// Helper functions for mining +// ------------------------------------------------------------------ +/* +static std::vector hexToBytes(const std::string& hexstr) { + if (hexstr.size() % 2 != 0) { + throw std::runtime_error("Hex prefix must have even length."); + } + std::vector bytes(hexstr.size() / 2); + for (size_t i = 0; i < bytes.size(); ++i) { + unsigned int val = 0; + std::stringstream ss; + ss << std::hex << hexstr.substr(i * 2, 2); + ss >> val; + bytes[i] = static_cast(val); + } + return bytes; +} +*/ + +inline bool hasPrefix(const std::vector& hash_output, const std::vector& prefix_bytes) { + if (prefix_bytes.size() > hash_output.size()) return false; + return std::equal(prefix_bytes.begin(), prefix_bytes.end(), hash_output.begin()); +} template -void invokeHash(HashAlgorithm algot, uint64_t seed, std::vector& buffer, std::vector& temp_out, int hash_size) { - if(algot == HashAlgorithm::Rainbow) { +void invokeHash(HashAlgorithm algot, uint64_t seed, std::vector& buffer, + std::vector& temp_out, int hash_size) { + if (algot == HashAlgorithm::Rainbow) { switch(hash_size) { case 64: rainbow::rainbow<64, bswap>(buffer.data(), buffer.size(), seed, temp_out.data()); @@ -22,7 +60,7 @@ void invokeHash(HashAlgorithm algot, uint64_t seed, std::vector& buffer default: throw std::runtime_error("Invalid hash_size for rainbow"); } - } else if(algot == HashAlgorithm::Rainstorm) { + } else if (algot == HashAlgorithm::Rainstorm) { switch(hash_size) { case 64: rainstorm::rainstorm<64, bswap>(buffer.data(), buffer.size(), seed, temp_out.data()); @@ -44,26 +82,177 @@ void invokeHash(HashAlgorithm algot, uint64_t seed, std::vector& buffer } } -void hashBuffer(Mode mode, HashAlgorithm algot, std::vector& buffer, uint64_t seed, uint64_t output_length, std::ostream& outstream, uint32_t hash_size) { +// ------------------------------------------------------------------ +// Mining Implementations +// ------------------------------------------------------------------ +static void chainAppend(std::vector& inputBuffer, const std::vector& hash_output) { + // Just append the new hash to the existing buffer + inputBuffer.insert(inputBuffer.end(), hash_output.begin(), hash_output.end()); +} + +void mineChain(HashAlgorithm algot, uint64_t seed, uint32_t hash_size, const std::vector& prefix_bytes) { + std::vector inputBuffer; + std::vector hash_output(hash_size / 8); + + uint64_t iterationCount = 0; + auto start_time = std::chrono::steady_clock::now(); + + while (true) { + iterationCount++; + invokeHash(algot, seed, inputBuffer, hash_output, hash_size); + + if (hasPrefix(hash_output, prefix_bytes)) { + auto end_time = std::chrono::steady_clock::now(); + double elapsed = std::chrono::duration(end_time - start_time).count(); + double hps = iterationCount / elapsed; + + std::cerr << "\n[mineChain] Found after " << iterationCount << " iterations, ~" + << hps << " H/s\n"; + + // Print final hash to stdout + std::cout << "Final Hash: "; + for (auto b : hash_output) { + std::cout << std::hex << std::setw(2) << std::setfill('0') << static_cast(b); + } + std::cout << std::dec << std::endl; + return; + } + chainAppend(inputBuffer, hash_output); + + if (iterationCount % 1000 == 0) { + auto now = std::chrono::steady_clock::now(); + double elapsed = std::chrono::duration(now - start_time).count(); + double hps = iterationCount / elapsed; + std::cerr << "\r[mineChain] " << iterationCount << " iterations, ~" + << hps << " H/s " << std::flush; + } + } +} + +void mineNonceInc(HashAlgorithm algot, uint64_t seed, uint32_t hash_size, + const std::vector& prefix_bytes, + const std::string& baseInput) { + std::vector hash_output(hash_size / 8); + uint64_t iterationCount = 0; + uint64_t nonce = 0; + + auto start_time = std::chrono::steady_clock::now(); + + while (true) { + iterationCount++; + + // Build input = baseInput + numeric nonce as text + std::stringstream ss; + ss << baseInput << nonce; + std::string inputStr = ss.str(); + + // Convert to bytes + std::vector buffer(inputStr.begin(), inputStr.end()); + invokeHash(algot, seed, buffer, hash_output, hash_size); + + if (hasPrefix(hash_output, prefix_bytes)) { + auto end_time = std::chrono::steady_clock::now(); + double elapsed = std::chrono::duration(end_time - start_time).count(); + double hps = iterationCount / elapsed; + + std::cerr << "\n[mineNonceInc] Found after " << iterationCount + << " iterations, ~" << hps << " H/s\n" + << "Winning nonce: " << nonce << "\n"; + + std::cout << "Final Hash: "; + for (auto b : hash_output) { + std::cout << std::hex << std::setw(2) << std::setfill('0') << static_cast(b); + } + std::cout << std::dec << "\n"; + return; + } + + if (iterationCount % 1000 == 0) { + auto now = std::chrono::steady_clock::now(); + double elapsed = std::chrono::duration(now - start_time).count(); + double hps = iterationCount / elapsed; + std::cerr << "\r[mineNonceInc] " << iterationCount << " iterations, ~" + << hps << " H/s " << std::flush; + } + + nonce++; + } +} + +void mineNonceRand(HashAlgorithm algot, uint64_t seed, uint32_t hash_size, + const std::vector& prefix_bytes, + const std::string& baseInput) { + std::vector hash_output(hash_size / 8); + uint64_t iterationCount = 0; + + static std::mt19937_64 rng(std::random_device{}()); + std::uniform_int_distribution dist(0, 255); + + auto start_time = std::chrono::steady_clock::now(); + + while (true) { + iterationCount++; + + // Build input = baseInput + random bytes + std::vector buffer(baseInput.begin(), baseInput.end()); + // e.g. 16 random bytes appended + for (int i = 0; i < 16; i++) { + buffer.push_back(dist(rng)); + } + + invokeHash(algot, seed, buffer, hash_output, hash_size); + + if (hasPrefix(hash_output, prefix_bytes)) { + auto end_time = std::chrono::steady_clock::now(); + double elapsed = std::chrono::duration(end_time - start_time).count(); + double hps = iterationCount / elapsed; + + std::cerr << "\n[mineNonceRand] Found after " << iterationCount + << " iterations, ~" << hps << " H/s\n"; + + std::cout << "Final Hash: "; + for (auto b : hash_output) { + std::cout << std::hex << std::setw(2) << std::setfill('0') << static_cast(b); + } + std::cout << std::dec << "\n"; + return; + } + + if (iterationCount % 1000 == 0) { + auto now = std::chrono::steady_clock::now(); + double elapsed = std::chrono::duration(now - start_time).count(); + double hps = iterationCount / elapsed; + std::cerr << "\r[mineNonceRand] " << iterationCount << " iterations, ~" + << hps << " H/s " << std::flush; + } + } +} + +// ------------------------------------------------------------------ +// Original hashBuffer +// ------------------------------------------------------------------ +void hashBuffer(Mode mode, HashAlgorithm algot, std::vector& buffer, + uint64_t seed, uint64_t output_length, std::ostream& outstream, + uint32_t hash_size) { int byte_size = hash_size / 8; std::vector temp_out(byte_size); - - if(mode == Mode::Digest) { + + if (mode == Mode::Digest) { invokeHash(algot, seed, buffer, temp_out, hash_size); - + for (const auto& byte : temp_out) { outstream << std::hex << std::setw(2) << std::setfill('0') << static_cast(byte); } } - else if(mode == Mode::Stream) { - while(output_length > 0) { + else if (mode == Mode::Stream) { + while (output_length > 0) { invokeHash(algot, seed, buffer, temp_out, hash_size); uint64_t chunk_size = std::min(output_length, (uint64_t)byte_size); outstream.write(reinterpret_cast(temp_out.data()), chunk_size); output_length -= chunk_size; - if(output_length == 0) { + if (output_length == 0) { break; } @@ -73,15 +262,21 @@ void hashBuffer(Mode mode, HashAlgorithm algot, std::vector& buffer, ui } } -void hashAnything(Mode mode, HashAlgorithm algot, const std::string& inpath, std::ostream& outstream, uint32_t size, bool use_test_vectors, uint64_t seed, uint64_t output_length) { +// ------------------------------------------------------------------ +// Original hashAnything +// ------------------------------------------------------------------ +void hashAnything(Mode mode, HashAlgorithm algot, const std::string& inpath, + std::ostream& outstream, uint32_t size, bool use_test_vectors, + uint64_t seed, uint64_t output_length) { + std::vector buffer; std::vector chunk(CHUNK_SIZE); if (use_test_vectors) { for (const auto& test_vector : test_vectors) { - buffer.assign(test_vector.begin(), test_vector.end()); - hashBuffer(mode, algot, buffer, seed, output_length, outstream, size); - outstream << ' ' << '"' << test_vector << '"' << '\n'; + buffer.assign(test_vector.begin(), test_vector.end()); + hashBuffer(mode, algot, buffer, seed, output_length, outstream, size); + outstream << ' ' << '"' << test_vector << '"' << '\n'; } } else { std::istream* in_stream = nullptr; @@ -90,61 +285,58 @@ void hashAnything(Mode mode, HashAlgorithm algot, const std::string& inpath, std uint64_t input_length = 0; if (!inpath.empty()) { - infile.open(inpath, std::ios::binary); - if (infile.fail()) { - throw std::runtime_error("Cannot open file for reading: " + inpath); - } - in_stream = &infile; - input_length = getFileSize(inpath); - - std::unique_ptr state; - if(algot == HashAlgorithm::Rainbow) { - state = std::make_unique(rainbow::HashState::initialize(seed, input_length, size)); - } else if(algot == HashAlgorithm::Rainstorm) { - state = std::make_unique(rainstorm::HashState::initialize(seed, input_length, size)); - } else { - throw std::runtime_error("Invalid algorithm: " + hashAlgoToString(algot)); - } + infile.open(inpath, std::ios::binary); + if (infile.fail()) { + throw std::runtime_error("Cannot open file for reading: " + inpath); + } + in_stream = &infile; + input_length = getFileSize(inpath); - // Stream the file in 16384-byte chunks - while (*in_stream) { - in_stream->read(reinterpret_cast(chunk.data()), CHUNK_SIZE); - if (in_stream->fail() && !in_stream->eof()) { - throw std::runtime_error("Input file could not be read after " + std::to_string(state->len) + " bytes processed."); - } - std::streamsize bytes_read = in_stream->gcount(); - if (bytes_read > 0 || input_length == 0) { - // Update the state with the new chunk of data - state->update(chunk.data(), bytes_read); - } - } + std::unique_ptr state; + if (algot == HashAlgorithm::Rainbow) { + state = std::make_unique( + rainbow::HashState::initialize(seed, input_length, size)); + } else if (algot == HashAlgorithm::Rainstorm) { + state = std::make_unique( + rainstorm::HashState::initialize(seed, input_length, size)); + } else { + throw std::runtime_error("Invalid algorithm: " + hashAlgoToString(algot)); + } - // Close the file if it's open - if (infile.is_open()) { - infile.close(); + // Stream the file + while (*in_stream) { + in_stream->read(reinterpret_cast(chunk.data()), CHUNK_SIZE); + if (in_stream->fail() && !in_stream->eof()) { + throw std::runtime_error( + "Input file could not be read after " + + std::to_string(state->len) + " bytes processed."); } + std::streamsize bytes_read = in_stream->gcount(); + if (bytes_read > 0 || input_length == 0) { + state->update(chunk.data(), bytes_read); + } + } - // Finalize the hash - std::vector output(output_length); - state->finalize(output.data()); - - //std::cout << "Length : " << state.len << std::endl; - //std::cout << "File length : " << input_length << std::endl; - + if (infile.is_open()) { + infile.close(); + } - // Write the output to the outstream + // Finalize + std::vector output(output_length); + state->finalize(output.data()); - if (mode == Mode::Digest) { - for (const auto& byte : output) { - outstream << std::hex << std::setw(2) << std::setfill('0') << static_cast(byte); - } - outstream << ' ' << (inpath.empty() ? "stdin" : inpath) << '\n'; - } else { - outstream.write(reinterpret_cast(output.data()), output_length); + if (mode == Mode::Digest) { + for (const auto& byte : output) { + outstream << std::hex << std::setw(2) << std::setfill('0') << static_cast(byte); } - } else { + outstream << ' ' << (inpath.empty() ? "stdin" : inpath) << '\n'; + } else { + outstream.write(reinterpret_cast(output.data()), output_length); + } + } + else { + // Read from stdin fully in_stream = &getInputStream(); - // Read all data into the buffer buffer = std::vector(std::istreambuf_iterator(*in_stream), {}); input_length = buffer.size(); hashBuffer(mode, algot, buffer, seed, output_length, outstream, size); @@ -153,121 +345,695 @@ void hashAnything(Mode mode, HashAlgorithm algot, const std::string& inpath, std } } -int main(int argc, char** argv) { - try { - cxxopts::Options options("rainsum", "Calculate a Rainbow or Rainstorm hash."); - auto seed_option = cxxopts::value()->default_value("0"); - - options.add_options() - ("m,mode", "Mode: digest or stream", cxxopts::value()->default_value("digest")) - ("v,version", "Print version") - ("a,algorithm", "Specify the hash algorithm to use", cxxopts::value()->default_value("bow")) - ("s,size", "Specify the size of the hash", cxxopts::value()->default_value("256")) - ("o,output-file", "Output file for the hash", cxxopts::value()->default_value("/dev/stdout")) - ("t,test-vectors", "Calculate the hash of the standard test vectors", cxxopts::value()->default_value("false")) - ("l,output-length", "Output length in hashes", cxxopts::value()->default_value("1000000")) - ("seed", "Seed value", seed_option) - ("h,help", "Print usage"); - - auto result = options.parse(argc, argv); - - if (result.count("version")) { - std::cout << "rainsum version: " << VERSION << '\n'; + +// ------------------------------------------------------------------ +// Helper for password (key) prompt - disabling echo on POSIX systems +// ------------------------------------------------------------------ +#ifdef _WIN32 +static std::string promptForKey(const std::string &prompt) { + std::cerr << prompt; + std::string key; + std::getline(std::cin, key); + return key; +} +#else +#include +#include +static std::string promptForKey(const std::string &prompt) { + std::cerr << prompt; + // Disable echo + termios oldt; + tcgetattr(STDIN_FILENO, &oldt); + termios noEcho = oldt; + noEcho.c_lflag &= ~ECHO; + tcsetattr(STDIN_FILENO, TCSANOW, &noEcho); + + std::string key; + std::getline(std::cin, key); + + // Restore echo + tcsetattr(STDIN_FILENO, TCSANOW, &oldt); + std::cerr << std::endl; + return key; +} +#endif + +/* compression helpers */ +#include +#include + +// Compress using zlib +std::vector compressData(const std::vector& data) { + z_stream zs = {}; + if (deflateInit(&zs, Z_BEST_COMPRESSION) != Z_OK) { + throw std::runtime_error("Failed to initialize zlib deflate."); + } + + zs.next_in = const_cast(data.data()); + zs.avail_in = data.size(); + + std::vector compressed; + uint8_t buffer[1024]; + + do { + zs.next_out = buffer; + zs.avail_out = sizeof(buffer); + + if (deflate(&zs, Z_FINISH) == Z_STREAM_ERROR) { + deflateEnd(&zs); + throw std::runtime_error("zlib compression failed."); } - if (result.count("help")) { - usage(); - return 0; + compressed.insert(compressed.end(), buffer, buffer + (sizeof(buffer) - zs.avail_out)); + } while (zs.avail_out == 0); + + deflateEnd(&zs); + return compressed; +} + +// Decompress using zlib +std::vector decompressData(const std::vector& data) { + z_stream zs = {}; + if (inflateInit(&zs) != Z_OK) { + throw std::runtime_error("Failed to initialize zlib inflate."); + } + + zs.next_in = const_cast(data.data()); + zs.avail_in = data.size(); + + std::vector decompressed; + uint8_t buffer[1024]; + + do { + zs.next_out = buffer; + zs.avail_out = sizeof(buffer); + + int ret = inflate(&zs, Z_NO_FLUSH); + if (ret == Z_STREAM_ERROR || ret == Z_DATA_ERROR || ret == Z_MEM_ERROR) { + inflateEnd(&zs); + throw std::runtime_error("zlib decompression failed."); } - std::string seed_str = result["seed"].as(); - uint64_t seed; - // If the seed_str can be converted to uint64_t, use it as a number; otherwise, hash it (with rainstorm, seed 0, 64-bit) - if (!(std::istringstream(seed_str) >> seed)) { - seed = hash_string_to_64_bit(seed_str); + decompressed.insert(decompressed.end(), buffer, buffer + (sizeof(buffer) - zs.avail_out)); + + if (ret == Z_STREAM_END) break; + } while (zs.avail_out == 0); + + inflateEnd(&zs); + return decompressed; +} + + +static void puzzleEncryptFileWithHeader( + const std::string &inFilename, + const std::string &outFilename, + const std::string &key, + HashAlgorithm algot, + uint32_t hash_size, + uint64_t seed, + size_t blockSize, + size_t nonceSize, + const std::string &searchMode +) { + // Open input file + std::ifstream fin(inFilename, std::ios::binary); + if (!fin.is_open()) { + throw std::runtime_error("Cannot open input file: " + inFilename); } - //std::cout << "Seed : " << seed << std::endl; + std::vector plainData((std::istreambuf_iterator(fin)), + (std::istreambuf_iterator())); + fin.close(); - Mode mode = result["mode"].as(); + // Open output file + std::ofstream fout(outFilename, std::ios::binary); + if (!fout.is_open()) { + throw std::runtime_error("Cannot open output file: " + outFilename); + } - // Check for output-length in Digest mode after parsing all options - if (mode == Mode::Digest && result.count("output-length")) { - std::cerr << "Error: --output-length is not allowed in digest mode.\n"; - return 1; + // Write header + uint32_t magicNumber = 0x52435259; // "RCRY" + uint8_t version = 0x01; + uint8_t blockSize8 = static_cast(blockSize); + uint8_t nonceSize8 = static_cast(nonceSize); + uint16_t digestSize16 = static_cast(hash_size); + uint8_t searchModeEnum = 0x00; // Default to 'prefix' + if (searchMode == "prefix") searchModeEnum = 0x00; + else if (searchMode == "sequence") searchModeEnum = 0x01; + else if (searchMode == "series" || searchMode == "scatter") searchModeEnum = 0x02; // Treat 'series' same as 'scatter' + else { + throw std::runtime_error("Invalid search mode: " + searchMode); } - std::string algorithm = result["algorithm"].as(); + std::string hashName = (algot == HashAlgorithm::Rainbow) ? "rainbow" : "rainstorm"; + uint8_t hashNameLength = static_cast(hashName.size()); - HashAlgorithm algot = getHashAlgorithm(algorithm); + fout.write(reinterpret_cast(&magicNumber), sizeof(magicNumber)); + fout.write(reinterpret_cast(&version), sizeof(version)); + fout.write(reinterpret_cast(&blockSize8), sizeof(blockSize8)); + fout.write(reinterpret_cast(&nonceSize8), sizeof(nonceSize8)); + fout.write(reinterpret_cast(&digestSize16), sizeof(digestSize16)); + fout.write(reinterpret_cast(&hashNameLength), sizeof(hashNameLength)); + fout.write(hashName.data(), hashName.size()); + fout.write(reinterpret_cast(&searchModeEnum), sizeof(searchModeEnum)); - uint32_t size = result["size"].as(); + // Compress plaintext + std::vector compressedData = compressData(plainData); + // Replace plainData with compressedData + plainData = std::move(compressedData); - if ( algot == HashAlgorithm::Rainbow ) { - switch (size) { - case 64: - case 128: - case 256: - break; - default: - throw std::runtime_error("Invalid hash_size for rainbow. Allowed sizes: 64, 128, 256"); - } - } else if ( algot == HashAlgorithm::Rainstorm ) { - switch (size) { - case 64: - case 128: - case 256: - case 512: - break; - default: - throw std::runtime_error("Invalid hash_size for rainstorm. Allowed sizes: 64, 128, 256, 512"); - } + // Update original size in the header to match compressed size + uint64_t originalSize = plainData.size(); + fout.write(reinterpret_cast(&originalSize), sizeof(originalSize)); + + // Partition plaintext into blocks + size_t totalBlocks = (plainData.size() + blockSize - 1) / blockSize; + std::vector hashOut(hash_size / 8); + std::vector keyBuf(key.begin(), key.end()); + + // Prepare random generator for nonce + std::mt19937_64 rng(std::random_device{}()); + std::uniform_int_distribution dist; + + size_t remaining = originalSize; + + for (size_t blockIndex = 0; blockIndex < totalBlocks; blockIndex++) { + std::bitset<64> usedIndices; + size_t thisBlockSize = std::min(blockSize, remaining); + remaining -= thisBlockSize; // Corrected subtraction + std::vector block( + plainData.begin() + blockIndex * blockSize, + plainData.begin() + blockIndex * blockSize + thisBlockSize + ); + + bool found = false; + std::vector chosenNonce(nonceSize, 0); + std::vector scatterIndices(thisBlockSize, 0); // Changed to uint8_t + + for (uint64_t tries = 0; ; tries++) { // No fixed MAX_TRIES + // Generate random nonce + for (size_t i = 0; i < nonceSize; i++) { + chosenNonce[i] = static_cast(dist(rng) & 0xFF); + } + + // Build trial buffer + std::vector trial(keyBuf); + trial.insert(trial.end(), chosenNonce.begin(), chosenNonce.end()); + + // Hash it + invokeHash(algot, seed, trial, hashOut, hash_size); + + if (searchModeEnum == 0x00) { // Prefix + if (hashOut.size() >= thisBlockSize && + std::equal(block.begin(), block.end(), hashOut.begin())) { + // Found at the front + scatterIndices.assign(thisBlockSize, 0); // All indices are 0 + found = true; + } + } + else if (searchModeEnum == 0x01) { // Sequence + // Search for block as a contiguous substring + uint8_t startIdx = 0; + for (size_t i = 0; i <= hashOut.size() - thisBlockSize; i++) { + if (std::equal(block.begin(), block.end(), hashOut.begin() + i)) { + startIdx = static_cast(i); // Ensure it fits in uint8_t + scatterIndices.assign(thisBlockSize, startIdx); // All bytes share the same start index + found = true; + break; + } + } + } + else if (searchModeEnum == 0x02) { // Series/Scatter + bool allFound = true; + usedIndices.reset(); // Clear used indices for the current block + + for (size_t byteIdx = 0; byteIdx < thisBlockSize; byteIdx++) { + auto it = hashOut.begin(); + + // Loop to find a valid index for the current byte + while (it != hashOut.end()) { + // Find the next occurrence of block[byteIdx] + it = std::find(it, hashOut.end(), block[byteIdx]); + + if (it != hashOut.end()) { // Found a match + uint8_t idx = static_cast(std::distance(hashOut.begin(), it)); + + if (!usedIndices.test(idx)) { // Ensure the index is not already used + scatterIndices[byteIdx] = idx; // Store the index + usedIndices.set(idx); // Mark it as used + break; // Exit loop for this byte + } + + // Advance to the next position for further search + ++it; + } + } + + if (it == hashOut.end()) { // No valid index found for this byte + allFound = false; + break; + } + } + + if (allFound) { + found = true; + } + } + + if (found) break; + + // Optional: Implement a maximum number of tries to prevent infinite loops + // Example: + /* + if (tries > MAX_TRIES) { + throw std::runtime_error("Failed to find a suitable nonce for block " + std::to_string(blockIndex)); + } + */ + + if (tries % 1'000'000 == 0) { + std::cerr << "\r[Enc] Block " << blockIndex << ", " << tries << " tries... " << std::flush; + } + } + + // Write nonce and indices + fout.write(reinterpret_cast(chosenNonce.data()), nonceSize); + if (searchModeEnum == 0x02) { // Series/Scatter + // Write each index as uint8_t + fout.write(reinterpret_cast(scatterIndices.data()), scatterIndices.size() * sizeof(uint8_t)); + } + else { // Prefix/Sequence + // Write single start index + uint8_t startIndex = scatterIndices[0]; + fout.write(reinterpret_cast(&startIndex), sizeof(startIndex)); + } } - bool use_test_vectors = result["test-vectors"].as(); - uint64_t output_length = result["output-length"].as(); + fout.close(); + std::cout << "[Enc] Encryption complete: " << outFilename << "\n"; +} - if ( mode == Mode::Digest ) { - output_length = size / 8; - } else { - output_length *= size; +static void puzzleDecryptFileWithHeader( + const std::string &inFilename, + const std::string &outFilename, + const std::string &key, + uint64_t seed +) { + // Open input file + std::ifstream fin(inFilename, std::ios::binary); + if (!fin.is_open()) { + throw std::runtime_error("Cannot open ciphertext file: " + inFilename); } - std::string inpath; + // Read header + uint32_t magicNumber; + uint8_t version; + uint8_t blockSize; + uint8_t nonceSize; + uint16_t hash_size; + uint8_t hashNameLength; + std::string hashName; + uint8_t searchModeEnum; + uint64_t originalSize; + + fin.read(reinterpret_cast(&magicNumber), sizeof(magicNumber)); + fin.read(reinterpret_cast(&version), sizeof(version)); + fin.read(reinterpret_cast(&blockSize), sizeof(blockSize)); + fin.read(reinterpret_cast(&nonceSize), sizeof(nonceSize)); + fin.read(reinterpret_cast(&hash_size), sizeof(hash_size)); + fin.read(reinterpret_cast(&hashNameLength), sizeof(hashNameLength)); - // Check if unmatched arguments exist (indicating there was a filename provided) - if(!result.unmatched().empty()) { - inpath = result.unmatched().front(); - } else { - // No filename provided, read from stdin + hashName.resize(hashNameLength); + fin.read(&hashName[0], hashNameLength); + fin.read(reinterpret_cast(&searchModeEnum), sizeof(searchModeEnum)); + fin.read(reinterpret_cast(&originalSize), sizeof(originalSize)); + + if (magicNumber != 0x52435259) { // "RCRY" + throw std::runtime_error("Invalid magic number in header."); } - std::string outpath = result["output-file"].as(); + HashAlgorithm algot = (hashName == "rainbow") ? HashAlgorithm::Rainbow : + (hashName == "rainstorm") ? HashAlgorithm::Rainstorm : + HashAlgorithm::Unknown; - if (outpath == "/dev/stdout") { - if (mode == Mode::Digest) { - hashAnything(Mode::Digest, algot, inpath, std::cout, size, use_test_vectors, seed, size / 8); - } else { - hashAnything(Mode::Stream, algot, inpath, std::cout, size, use_test_vectors, seed, output_length); - } - } else { - std::ofstream outfile(outpath, std::ios::binary); - if (!outfile.is_open()) { - std::cerr << "Failed to open output file: " << outpath << std::endl; - return 1; - } + if (algot == HashAlgorithm::Unknown) { + throw std::runtime_error("Unsupported hash algorithm in header."); + } - if (mode == Mode::Digest) { - hashAnything(Mode::Digest, algot, inpath, outfile, size, use_test_vectors, seed, size / 8); - } else { - hashAnything(Mode::Stream, algot, inpath, outfile, size, use_test_vectors, seed, output_length); - } + // Read the rest of the file as cipher data + std::vector cipherData( + (std::istreambuf_iterator(fin)), + (std::istreambuf_iterator()) + ); + fin.close(); + + // Verify cipherData size + size_t totalBlocks = (originalSize + blockSize - 1) / blockSize; + size_t cipherOffset = 0; + + // Prepare to reconstruct plaintext + std::vector keyBuf(key.begin(), key.end()); + std::vector hashOut(hash_size / 8); + std::vector plaintextAccumulated; // Collect all decrypted blocks + + size_t recoveredSoFar = 0; + + for (size_t blockIndex = 0; blockIndex < totalBlocks; blockIndex++) { + size_t thisBlockSize = std::min(blockSize, originalSize - recoveredSoFar); + std::vector block; + + // Extract nonce + std::vector storedNonce(cipherData.begin() + cipherOffset, + cipherData.begin() + cipherOffset + nonceSize); + cipherOffset += nonceSize; - outfile.close(); + // Read indices based on search mode + std::vector scatterIndices; + uint8_t startIndex = 0; + if (searchModeEnum == 0x02) { // Series/Scatter + scatterIndices.resize(thisBlockSize); + std::memcpy(scatterIndices.data(), &cipherData[cipherOffset], thisBlockSize * sizeof(uint8_t)); + cipherOffset += thisBlockSize * sizeof(uint8_t); + } else { // Prefix/Sequence + std::memcpy(&startIndex, &cipherData[cipherOffset], sizeof(uint8_t)); + cipherOffset += sizeof(uint8_t); + } + + // Recompute the hash + std::vector trial(keyBuf); + trial.insert(trial.end(), storedNonce.begin(), storedNonce.end()); + invokeHash(algot, seed, trial, hashOut, hash_size); + + // Reconstruct plaintext block based on search mode + if (searchModeEnum == 0x00) { // Prefix + block.assign(hashOut.begin(), hashOut.begin() + thisBlockSize); + } else if (searchModeEnum == 0x01) { // Sequence + if (startIndex + thisBlockSize > hashOut.size()) { + throw std::runtime_error("[Dec] Start index out of bounds in sequence mode."); + } + block.assign(hashOut.begin() + startIndex, hashOut.begin() + startIndex + thisBlockSize); + } else if (searchModeEnum == 0x02) { // Series/Scatter + block.reserve(thisBlockSize); + for (size_t j = 0; j < thisBlockSize; j++) { + uint8_t idx = scatterIndices[j]; + if (idx >= hashOut.size()) { + throw std::runtime_error("[Dec] Scatter index out of range."); + } + block.push_back(hashOut[idx]); + } + } else { + throw std::runtime_error("Invalid search mode enum in decryption."); + } + + // Append block to accumulated plaintext + plaintextAccumulated.insert(plaintextAccumulated.end(), block.begin(), block.end()); + recoveredSoFar += thisBlockSize; + + if (blockIndex % 100 == 0) { + std::cerr << "\r[Dec] Processing block " << blockIndex << " / " << totalBlocks << std::flush; + } } - return 0; - } catch (const std::runtime_error& e) { - std::cerr << "An error occurred: " << e.what() << std::endl; - } + std::cout << "\n[Dec] Ciphertext blocks decrypted successfully.\n"; + + // Decompress accumulated plaintext + std::vector decompressedData = decompressData(plaintextAccumulated); + + // Write decompressed data to the output file + std::ofstream fout(outFilename, std::ios::binary); + if (!fout.is_open()) { + throw std::runtime_error("Cannot open output file for decompressed plaintext: " + outFilename); + } + + fout.write(reinterpret_cast(decompressedData.data()), decompressedData.size()); + fout.close(); + + std::cout << "[Dec] Decompressed plaintext written to: " << outFilename << "\n"; +} + +// The main function +int main(int argc, char** argv) { + try { + // Initialize cxxopts with program name and description + cxxopts::Options options("rainsum", "Calculate a Rainbow or Rainstorm hash, or perform puzzle-based encryption/decryption."); + + // Define command-line options + options.add_options() + ("m,mode", "Mode: digest, stream, enc, dec", + cxxopts::value()->default_value("digest")) + ("v,version", "Print version") + ("a,algorithm", "Specify the hash algorithm to use (rainbow, rainstorm)", + cxxopts::value()->default_value("bow")) + ("s,size", "Specify the size of the hash (e.g., 64, 128, 256, 512)", + cxxopts::value()->default_value("256")) + ("block-size", "Block size in bytes for puzzle-based encryption (1-255)", + cxxopts::value()->default_value("3")) + ("n,nonce-size", "Size of the nonce in bytes (1-255)", + cxxopts::value()->default_value("8")) + ("search-mode", "Search mode: prefix, sequence, series, scatter", + cxxopts::value()->default_value("prefix")) + ("o,output-file", "Output file", + cxxopts::value()->default_value("/dev/stdout")) + ("t,test-vectors", "Calculate the hash of the standard test vectors", + cxxopts::value()->default_value("false")) + ("l,output-length", "Output length in hash iterations (stream mode)", + cxxopts::value()->default_value("1000000")) + ("seed", "Seed value", + cxxopts::value()->default_value("0")) + // Mining options + ("mine-mode", "Mining mode: chain, nonceInc, nonceRand", + cxxopts::value()->default_value("None")) + ("match-prefix", "Hex prefix to match in mining tasks", + cxxopts::value()->default_value("")) + // Encrypt / Decrypt options + ("P,password", "Encryption/Decryption password", + cxxopts::value()->default_value("")) + ("h,help", "Print usage"); + + // Parse command-line options + auto result = options.parse(argc, argv); + + // Handle help and version immediately + if (result.count("help")) { + std::cout << options.help() << "\n"; + return 0; + } + + if (result.count("version")) { + std::cout << "rainsum version: 1.0.0\n"; // Replace with actual VERSION + return 0; + } + + // Access and validate options + + // Hash Size + uint32_t hash_size = result["size"].as(); + + // Block Size + uint8_t blockSize = result["block-size"].as(); + if (blockSize == 0 || blockSize > 255) { + throw std::runtime_error("Block size must be between 1 and 255 bytes."); + } + + // Nonce Size + size_t nonceSize = result["nonce-size"].as(); + if (nonceSize == 0 || nonceSize > 255) { + throw std::runtime_error("Nonce size must be between 1 and 255 bytes."); + } + + // Search Mode + std::string searchMode = result["search-mode"].as(); + if (searchMode != "prefix" && searchMode != "sequence" && + searchMode != "series" && searchMode != "scatter") { + throw std::runtime_error("Invalid search mode: " + searchMode); + } + + // Convert Seed (either numeric or hash string) + std::string seed_str = result["seed"].as(); + uint64_t seed; + if (!(std::istringstream(seed_str) >> seed)) { + seed = hash_string_to_64_bit(seed_str); + } + + // Determine Mode + std::string modeStr = result["mode"].as(); + Mode mode; + if (modeStr == "digest") mode = Mode::Digest; + else if (modeStr == "stream") mode = Mode::Stream; + else if (modeStr == "enc") mode = Mode::Enc; + else if (modeStr == "dec") mode = Mode::Dec; + else throw std::runtime_error("Invalid mode: " + modeStr); + + // Determine Hash Algorithm + std::string algorithm = result["algorithm"].as(); + HashAlgorithm algot = getHashAlgorithm(algorithm); + if (algot == HashAlgorithm::Unknown) { + throw std::runtime_error("Unsupported algorithm string: " + algorithm); + } + + // Validate Hash Size based on Algorithm + if (algot == HashAlgorithm::Rainbow) { + if (hash_size != 64 && hash_size != 128 && hash_size != 256) { + throw std::runtime_error("Invalid size for Rainbow (must be 64, 128, or 256)."); + } + } + else if (algot == HashAlgorithm::Rainstorm) { + if (hash_size != 64 && hash_size != 128 && hash_size != 256 && hash_size != 512) { + throw std::runtime_error("Invalid size for Rainstorm (must be 64, 128, 256, or 512)."); + } + } + + // Test Vectors + bool use_test_vectors = result["test-vectors"].as(); + + // Output Length + uint64_t output_length = result["output-length"].as(); + + // Adjust output_length based on mode + if (mode == Mode::Digest) { + output_length = hash_size / 8; + } + else if (mode == Mode::Stream) { + output_length *= hash_size / 8; + } + + // Mining arguments + std::string mine_mode_str = result["mine-mode"].as(); + MineMode mine_mode; + if (mine_mode_str == "chain") mine_mode = MineMode::Chain; + else if (mine_mode_str == "nonceInc") mine_mode = MineMode::NonceInc; + else if (mine_mode_str == "nonceRand") mine_mode = MineMode::NonceRand; + else mine_mode = MineMode::None; + + std::string prefixHex = result["match-prefix"].as(); + + // Unmatched arguments might be input file + std::string inpath; + if (!result.unmatched().empty()) { + inpath = result.unmatched().front(); + } + + std::string password = result["password"].as(); + + + // Handle Mining Modes + if (mine_mode != MineMode::None) { + if (prefixHex.empty()) { + throw std::runtime_error("You must specify --match-prefix for mining modes."); + } + + // Convert prefix from hex + auto hexToBytes = [&](const std::string &hexstr) -> std::vector { + if (hexstr.size() % 2 != 0) { + throw std::runtime_error("Hex prefix must have even length."); + } + std::vector bytes(hexstr.size() / 2); + for (size_t i = 0; i < bytes.size(); ++i) { + unsigned int val = 0; + std::stringstream ss; + ss << std::hex << hexstr.substr(i*2, 2); + ss >> val; + bytes[i] = static_cast(val); + } + return bytes; + }; + + std::vector prefixBytes = hexToBytes(prefixHex); + + switch (mine_mode) { + case MineMode::Chain: + mineChain(algot, seed, hash_size, prefixBytes); + return 0; + case MineMode::NonceInc: + mineNonceInc(algot, seed, hash_size, prefixBytes, inpath); + return 0; + case MineMode::NonceRand: + mineNonceRand(algot, seed, hash_size, prefixBytes, inpath); + return 0; + default: + throw std::runtime_error("Invalid mine-mode encountered."); + } + } + + // Normal Hashing or Encryption/Decryption + std::string outpath = result["output-file"].as(); + + if (mode == Mode::Digest) { + // Just a normal digest + if (outpath == "/dev/stdout") { + hashAnything(Mode::Digest, algot, inpath, std::cout, hash_size, use_test_vectors, seed, output_length); + } + else { + std::ofstream outfile(outpath, std::ios::binary); + if (!outfile.is_open()) { + std::cerr << "Failed to open output file: " << outpath << std::endl; + return 1; + } + hashAnything(Mode::Digest, algot, inpath, outfile, hash_size, use_test_vectors, seed, output_length); + } + } + else if (mode == Mode::Stream) { + if (outpath == "/dev/stdout") { + hashAnything(Mode::Stream, algot, inpath, std::cout, hash_size, use_test_vectors, seed, output_length); + } + else { + std::ofstream outfile(outpath, std::ios::binary); + if (!outfile.is_open()) { + std::cerr << "Failed to open output file: " << outpath << std::endl; + return 1; + } + hashAnything(Mode::Stream, algot, inpath, outfile, hash_size, use_test_vectors, seed, output_length); + } + } + else if (mode == Mode::Enc) { + if (inpath.empty()) { + throw std::runtime_error("No input file specified for encryption."); + } + std::string key; + if (!password.empty()) { + key = password; + } + else { + key = promptForKey("Enter encryption key: "); + } + + // We'll write ciphertext to inpath + ".rc" + std::string encFile = inpath + ".rc"; + puzzleEncryptFileWithHeader(inpath, encFile, key, algot, hash_size, seed, blockSize, nonceSize, searchMode); + std::cout << "[Enc] Wrote encrypted file to: " << encFile << "\n"; + } + else if (mode == Mode::Dec) { + // Puzzle-based decryption (block-based) + if (inpath.empty()) { + throw std::runtime_error("No ciphertext file specified for decryption."); + } + std::string key; + if (!password.empty()) { + key = password; + } + else { + key = promptForKey("Enter encryption key: "); + } + + // We'll write plaintext to inpath + ".dec" + std::string decFile = inpath + ".dec"; + puzzleDecryptFileWithHeader(inpath, decFile, key, seed); + std::cout << "[Dec] Wrote decrypted file to: " << decFile << "\n"; + } + + return 0; + } + catch (const cxxopts::exceptions::exception& e) { + std::cerr << "Error parsing options: " << e.what() << "\n"; + return 1; + } + catch (const std::bad_cast &e) { + std::cerr << "Bad cast during option parsing: " << e.what() << "\n"; + return 1; + } + catch (const std::runtime_error &e) { + std::cerr << "An error occurred: " << e.what() << std::endl; + return 1; + } } + + diff --git a/src/tool.h b/src/tool.h index 5b7d3c7..9072f50 100644 --- a/src/tool.h +++ b/src/tool.h @@ -1,3 +1,5 @@ +#pragma once + #include #include @@ -6,88 +8,175 @@ #include #endif - #include "rainbow.cpp" #include "rainstorm.cpp" #include "cxxopts.hpp" #include "common.h" -#define VERSION "1.2.2" +#define VERSION "1.3.0" enum class Mode { Digest, - Stream + Stream, + Enc, // added + Dec // added }; std::string modeToString(const Mode& mode) { - switch(mode) { - case Mode::Digest: return "Digest"; - case Mode::Stream: return "Stream"; - default: throw std::runtime_error("Unknown hash mode (expected digest or stream)"); - } + switch(mode) { + case Mode::Digest: return "Digest"; + case Mode::Stream: return "Stream"; + case Mode::Enc: return "Enc"; // added + case Mode::Dec: return "Dec"; // added + default: throw std::runtime_error("Unknown hash mode"); + } } std::istream& operator>>(std::istream& in, Mode& mode) { std::string token; in >> token; - if (token == "digest") - mode = Mode::Digest; - else if (token == "stream") - mode = Mode::Stream; - else + if (token == "digest") mode = Mode::Digest; + else if (token == "stream") mode = Mode::Stream; + else if (token == "enc") mode = Mode::Enc; // added + else if (token == "dec") mode = Mode::Dec; // added + else in.setstate(std::ios_base::failbit); + return in; +} + + +// Mining mode enum +enum class MineMode { + None, + Chain, + NonceInc, + NonceRand +}; + +std::string mineModeToString(const MineMode& mode) { + switch(mode) { + case MineMode::None: return "None"; + case MineMode::Chain: return "Chain"; + case MineMode::NonceInc: return "NonceInc"; // added + case MineMode::NonceRand: return "NonceRand"; // added + default: throw std::runtime_error("Unknown mine mode"); + } +} + +// ------------------------------------------------------------------ +// Mining Mode Operator>>(std::istream&, MineMode&) Implementation +// (If you haven't placed this in tool.h, it can go here) +// ------------------------------------------------------------------ +std::istream& operator>>(std::istream& in, MineMode& mode) { + std::string token; + in >> token; + if (token == "chain") { + mode = MineMode::Chain; + } else if (token == "nonceInc") { + mode = MineMode::NonceInc; + } else if (token == "nonceRand") { + mode = MineMode::NonceRand; + } else { + mode = MineMode::None; + } + return in; +} + +enum SearchMode { + Prefix, + Sequence, + Series, + Scatter +}; + +std::string searchModeToString(const SearchMode& mode) { + switch(mode) { + case SearchMode::Prefix: return "Prefix"; + case SearchMode::Sequence: return "Sequence"; + case SearchMode::Series: return "Series"; // added + case SearchMode::Scatter: return "Scatter"; // added + default: throw std::runtime_error("Unknown search mode"); + } +} + +std::istream& operator>>(std::istream& in, SearchMode& mode) { + std::string token; + in >> token; + if (token == "prefix") { + mode = SearchMode::Prefix; + } else if (token == "Sequence") { + mode = SearchMode::Sequence; + } else if (token == "Series") { + mode = SearchMode::Series; + } else if (token == "Scatter" ) { + mode = SearchMode::Scatter; + } else { in.setstate(std::ios_base::failbit); + } return in; } +// Stream retrieval (stdin vs file) std::istream& getInputStream() { - #ifdef _WIN32 - // On Windows, use std::cin - return std::cin; - #else - // On Unix-based systems, use std::ifstream with /dev/stdin - static std::ifstream in("/dev/stdin"); - return in; - #endif +#ifdef _WIN32 + // On Windows, use std::cin + return std::cin; +#else + // On Unix-based systems, use std::ifstream with /dev/stdin + static std::ifstream in("/dev/stdin"); + return in; +#endif } enum class HashAlgorithm { - Rainbow, - Rainstorm, - Unknown + Rainbow, + Rainstorm, + Unknown }; std::string hashAlgoToString(const HashAlgorithm& algo) { - switch(algo) { - case HashAlgorithm::Rainbow: return "Rainbow"; - case HashAlgorithm::Rainstorm: return "Rainstorm"; - default: throw std::runtime_error("Unknown hash algorithm value (expected rainbow or rainstorm)"); - } + switch(algo) { + case HashAlgorithm::Rainbow: return "Rainbow"; + case HashAlgorithm::Rainstorm: return "Rainstorm"; + default: + throw std::runtime_error("Unknown hash algorithm value (expected rainbow or rainstorm)"); + } } - +// Convert string -> HashAlgorithm HashAlgorithm getHashAlgorithm(const std::string& algorithm) { - if (algorithm == "rainbow" || algorithm == "bow") { - return HashAlgorithm::Rainbow; - } else if (algorithm == "rainstorm" || algorithm == "storm") { - return HashAlgorithm::Rainstorm; - } else { - return HashAlgorithm::Unknown; - } + if (algorithm == "rainbow" || algorithm == "bow") { + return HashAlgorithm::Rainbow; + } else if (algorithm == "rainstorm" || algorithm == "storm") { + return HashAlgorithm::Rainstorm; + } else { + return HashAlgorithm::Unknown; + } } -// Prototype of functions +// Prototypes void usage(); -void hashBuffer(Mode mode, HashAlgorithm algot, std::vector& buffer, uint64_t seed, uint64_t output_length, std::ostream& outstream, uint32_t hash_size); -void hashAnything(Mode mode, HashAlgorithm algot , const std::string& inpath, const std::string& outpath, uint32_t size, bool use_test_vectors, uint64_t seed, uint64_t output_length); + +void hashBuffer(Mode mode, HashAlgorithm algot, + std::vector& buffer, uint64_t seed, + uint64_t output_length, std::ostream& outstream, + uint32_t hash_size); + +void hashAnything(Mode mode, HashAlgorithm algot, + const std::string& inpath, std::ostream& outstream, + uint32_t size, bool use_test_vectors, + uint64_t seed, uint64_t output_length); + std::string generate_filename(const std::string& filename); + uint64_t hash_string_to_64_bit(const std::string& seed_str); +// For cxxopts: getFileSize usage uint64_t getFileSize(const std::string& filename) { - struct stat st; - if(stat(filename.c_str(), &st) != 0) { - return 0; // You may want to handle this error differently - } - return st.st_size; + struct stat st; + if(stat(filename.c_str(), &st) != 0) { + return 0; // You may want to handle this error differently + } + return st.st_size; } #ifdef USE_FILESYSTEM @@ -102,7 +191,8 @@ std::string generate_filename(const std::string& filename) { // Handle filename collision int counter = 1; while (std::filesystem::exists(new_filename)) { - new_filename = p.stem().string() + timestamp + "-" + std::to_string(counter++) + p.extension().string(); + new_filename = p.stem().string() + timestamp + "-" + std::to_string(counter++) + + p.extension().string(); } } else { new_filename = filename; @@ -110,21 +200,21 @@ std::string generate_filename(const std::string& filename) { return new_filename; } - #else -// If filesystem isn't available, just return the filename as it is +// If filesystem isn't available, just return the filename as is std::string generate_filename(const std::string& filename) { return filename; } #endif uint64_t hash_string_to_64_bit(const std::string& seed_str) { - std::vector buffer(seed_str.begin(), seed_str.end()); - std::vector hash_output(8); // 64 bits = 8 bytes - rainstorm::rainstorm<64, bswap>(buffer.data(), buffer.size(), 0, hash_output.data()); // assuming bswap is defined elsewhere - uint64_t seed = 0; - std::memcpy(&seed, hash_output.data(), 8); - return seed; + std::vector buffer(seed_str.begin(), seed_str.end()); + std::vector hash_output(8); // 64 bits = 8 bytes + // We'll use 64-bit rainstorm for string -> seed + rainstorm::rainstorm<64, bswap>(buffer.data(), buffer.size(), 0, hash_output.data()); + uint64_t seed = 0; + std::memcpy(&seed, hash_output.data(), 8); + return seed; } void usage() { @@ -132,7 +222,7 @@ void usage() { << "Calculate a Rainbow or Rainstorm hash.\n\n" << "Options:\n" << " -m, --mode [digest|stream] Specifies the mode, where:\n" - << " digest mode (the default) gives a fixed length hash in hex, or\n" + << " digest mode (the default) gives a fixed length hash in hex,\n" << " stream mode gives a variable length binary feedback output\n" << " -a, --algorithm [bow|storm] Specify the hash algorithm to use. Default: storm\n" << " -s, --size [64-256|64-512] Specify the bit size of the hash. Default: 256\n" @@ -141,15 +231,19 @@ void usage() { << " -l, --output-length HASHES Set the output length in hash iterations (stream only)\n" << " -v, --version Print out the version\n" << " --seed Seed value (64-bit number or string). If string is used,\n" - << " it is hashed with Rainstorm to a 64-bit number\n"; + << " it is hashed with Rainstorm to a 64-bit number\n" + << " --mine-mode [chain|nonceInc|nonceRand] Perform 'mining' tasks until prefix is matched\n" + << " --match-prefix Hex prefix to match for mining tasks\n"; } -// test vectors -std::vector test_vectors = {"", - "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", - "The quick brown fox jumps over the lazy dog", - "The quick brown fox jumps over the lazy cog", - "The quick brown fox jumps over the lazy dog.", - "After the rainstorm comes the rainbow.", - std::string(64, '@')}; +// Standard test vectors +std::vector test_vectors = { + "", + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", + "The quick brown fox jumps over the lazy dog", + "The quick brown fox jumps over the lazy cog", + "The quick brown fox jumps over the lazy dog.", + "After the rainstorm comes the rainbow.", + std::string(64, '@') +};