diff --git a/src/seraphis_crypto/sp_transcript.h b/src/seraphis_crypto/sp_transcript.h new file mode 100644 index 000000000..c2fbd88c2 --- /dev/null +++ b/src/seraphis_crypto/sp_transcript.h @@ -0,0 +1,397 @@ +// Copyright (c) 2022, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Transcript class for assembling data that needs to be hashed. + +#pragma once + +//local headers +#include "crypto/crypto.h" +#include "cryptonote_config.h" +#include "ringct/rctTypes.h" +#include "wipeable_string.h" + +//third party headers +#include + +//standard headers +#include +#include +#include +#include + +//forward declarations + + +namespace sp +{ + +//// +// SpTranscriptBuilder +// - build a transcript +// - the user must provide a label when trying to append something to the transcript; labels are prepended to objects in +// the transcript +// - data types +// - unsigned int: uint_flag || varint(uint_variable) +// - signed int: int_flag || uchar{int_variable < 0 ? 1 : 0} || varint(abs(int_variable)) +// - byte buffer (assumed little-endian): buffer_flag || buffer_length || buffer +// - all labels are treated as byte buffers +// - named container: container_flag || container_name || data_member1 || ... || container_terminator_flag +// - list-type container (same-type elements only): list_flag || list_length || element1 || element2 || ... +// - the transcript can be used like a string via the .data() and .size() member functions +// - simple mode: exclude all labels, flags, and lengths +/// +class SpTranscriptBuilder final +{ +public: +//public member types + /// transcript builder mode + enum class Mode + { + FULL, + SIMPLE + }; + +//constructors + /// normal constructor + SpTranscriptBuilder(const std::size_t estimated_data_size, const Mode mode) : + m_mode{mode} + { + m_transcript.reserve(2 * estimated_data_size + 20); + } + +//overloaded operators + /// disable copy/move (this is a scoped manager [of the 'transcript' concept]) + SpTranscriptBuilder& operator=(SpTranscriptBuilder&&) = delete; + +//member functions + /// append a value to the transcript + template + void append(const boost::string_ref label, const T &value) + { + this->append_impl(label, value); + } + + /// access the transcript data + const void* data() const { return m_transcript.data(); } + std::size_t size() const { return m_transcript.size(); } + +private: +//member variables + /// in simple mode, exclude labels, flags, and lengths + Mode m_mode; + /// the transcript buffer (wipeable in case it contains sensitive data) + epee::wipeable_string m_transcript; + +//private member types + /// flags for separating items added to the transcript + enum SpTranscriptBuilderFlag : unsigned char + { + UNSIGNED_INTEGER = 0, + SIGNED_INTEGER = 1, + BYTE_BUFFER = 2, + NAMED_CONTAINER = 3, + NAMED_CONTAINER_TERMINATOR = 4, + LIST_TYPE_CONTAINER = 5 + }; + +//transcript builders + void append_character(const unsigned char character) + { + m_transcript += static_cast(character); + } + void append_uint(const std::uint64_t unsigned_integer) + { + unsigned char v_variable[(sizeof(std::uint64_t) * 8 + 6) / 7]; + unsigned char *v_variable_end = v_variable; + + // append uint to string as a varint + tools::write_varint(v_variable_end, unsigned_integer); + assert(v_variable_end <= v_variable + sizeof(v_variable)); + m_transcript.append(reinterpret_cast(v_variable), v_variable_end - v_variable); + } + void append_flag(const SpTranscriptBuilderFlag flag) + { + if (m_mode == Mode::SIMPLE) + return; + + static_assert(sizeof(SpTranscriptBuilderFlag) == sizeof(unsigned char), ""); + this->append_character(static_cast(flag)); + } + void append_length(const std::size_t length) + { + if (m_mode == Mode::SIMPLE) + return; + + static_assert(sizeof(std::size_t) <= sizeof(std::uint64_t), ""); + this->append_uint(static_cast(length)); + } + void append_buffer(const void *data, const std::size_t length) + { + this->append_flag(SpTranscriptBuilderFlag::BYTE_BUFFER); + this->append_length(length); + m_transcript.append(reinterpret_cast(data), length); + } + void append_label(const boost::string_ref label) + { + if (m_mode == Mode::SIMPLE || + label.size() == 0) + return; + + this->append_buffer(label.data(), label.size()); + } + void append_labeled_buffer(const boost::string_ref label, const void *data, const std::size_t length) + { + this->append_label(label); + this->append_buffer(data, length); + } + void begin_named_container(const boost::string_ref container_name) + { + this->append_flag(SpTranscriptBuilderFlag::NAMED_CONTAINER); + this->append_label(container_name); + } + void end_named_container() + { + this->append_flag(SpTranscriptBuilderFlag::NAMED_CONTAINER_TERMINATOR); + } + void begin_list_type_container(const std::size_t list_length) + { + this->append_flag(SpTranscriptBuilderFlag::LIST_TYPE_CONTAINER); + this->append_length(list_length); + } + +//append overloads + void append_impl(const boost::string_ref label, const rct::key &key_buffer) + { + this->append_labeled_buffer(label, key_buffer.bytes, sizeof(key_buffer)); + } + void append_impl(const boost::string_ref label, const rct::ctkey &ctkey) + { + this->append_label(label); + this->append_impl("ctmask", ctkey.mask); + this->append_impl("ctdest", ctkey.dest); + } + void append_impl(const boost::string_ref label, const crypto::secret_key &point_buffer) + { + this->append_labeled_buffer(label, point_buffer.data, sizeof(point_buffer)); + } + void append_impl(const boost::string_ref label, const crypto::public_key &scalar_buffer) + { + this->append_labeled_buffer(label, scalar_buffer.data, sizeof(scalar_buffer)); + } + void append_impl(const boost::string_ref label, const crypto::key_derivation &derivation_buffer) + { + this->append_labeled_buffer(label, derivation_buffer.data, sizeof(derivation_buffer)); + } + void append_impl(const boost::string_ref label, const crypto::key_image &key_image_buffer) + { + this->append_labeled_buffer(label, key_image_buffer.data, sizeof(key_image_buffer)); + } + void append_impl(const boost::string_ref label, const std::string &string_buffer) + { + this->append_labeled_buffer(label, string_buffer.data(), string_buffer.size()); + } + void append_impl(const boost::string_ref label, const epee::wipeable_string &string_buffer) + { + this->append_labeled_buffer(label, string_buffer.data(), string_buffer.size()); + } + void append_impl(const boost::string_ref label, const boost::string_ref string_buffer) + { + this->append_labeled_buffer(label, string_buffer.data(), string_buffer.size()); + } + template + void append_impl(const boost::string_ref label, const unsigned char(&uchar_buffer)[Sz]) + { + this->append_labeled_buffer(label, uchar_buffer, Sz); + } + template + void append_impl(const boost::string_ref label, const char(&char_buffer)[Sz]) + { + this->append_labeled_buffer(label, char_buffer, Sz); + } + void append_impl(const boost::string_ref label, const std::vector &vector_buffer) + { + this->append_labeled_buffer(label, vector_buffer.data(), vector_buffer.size()); + } + void append_impl(const boost::string_ref label, const std::vector &vector_buffer) + { + this->append_labeled_buffer(label, vector_buffer.data(), vector_buffer.size()); + } + void append_impl(const boost::string_ref label, const char single_character) + { + this->append_label(label); + this->append_character(static_cast(single_character)); + } + void append_impl(const boost::string_ref label, const unsigned char single_character) + { + this->append_label(label); + this->append_character(single_character); + } + template::value, bool> = true> + void append_impl(const boost::string_ref label, const T unsigned_integer) + { + static_assert(sizeof(T) <= sizeof(std::uint64_t), "SpTranscriptBuilder: unsupported unsigned integer type."); + this->append_label(label); + this->append_flag(SpTranscriptBuilderFlag::UNSIGNED_INTEGER); + this->append_uint(unsigned_integer); + } + template::value, bool> = true, + std::enable_if_t::value, bool> = true> + void append_impl(const boost::string_ref label, const T signed_integer) + { + using unsigned_type = std::make_unsigned::type; + static_assert(sizeof(unsigned_type) <= sizeof(std::uint64_t), + "SpTranscriptBuilder: unsupported signed integer type."); + this->append_label(label); + this->append_flag(SpTranscriptBuilderFlag::SIGNED_INTEGER); + this->append_uint(static_cast(static_cast(signed_integer))); + } + template::value, bool> = true> + void append_impl(const boost::string_ref label, const T &named_container) + { + // named containers must satisfy two concepts: + // const boost::string_ref container_name(const T &container); + // void append_to_transcript(const T &container, SpTranscriptBuilder &transcript_inout); + //todo: container_name() and append_to_transcript() must be defined in the sp namespace, but that is not generic + this->append_label(label); + this->begin_named_container(container_name(named_container)); + append_to_transcript(named_container, *this); //non-member function assumed to be implemented elsewhere + this->end_named_container(); + } + template + void append_impl(const boost::string_ref label, const std::vector &list_container) + { + this->append_label(label); + this->begin_list_type_container(list_container.size()); + for (const T &element : list_container) + this->append_impl("", element); + } + template + void append_impl(const boost::string_ref label, const std::list &list_container) + { + this->append_label(label); + this->begin_list_type_container(list_container.size()); + for (const T &element : list_container) + this->append_impl("", element); + } +}; + +//// +// SpFSTranscript +// - build a Fiat-Shamir transcript +// - main format: prefix || domain_separator || object1_label || object1 || object2_label || object2 || ... +// note: prefix defaults to "monero" +/// +class SpFSTranscript final +{ +public: +//constructors + /// normal constructor: start building a transcript with the domain separator + SpFSTranscript(const boost::string_ref domain_separator, + const std::size_t estimated_data_size, + const boost::string_ref prefix = config::TRANSCRIPT_PREFIX) : + m_transcript_builder{15 + domain_separator.size() + estimated_data_size + prefix.size(), + SpTranscriptBuilder::Mode::FULL} + { + // transcript = prefix || domain_separator + m_transcript_builder.append("FSp", prefix); + m_transcript_builder.append("ds", domain_separator); + } + +//overloaded operators + /// disable copy/move (this is a scoped manager [of the 'transcript' concept]) + SpFSTranscript& operator=(SpFSTranscript&&) = delete; + +//member functions + /// build the transcript + template + void append(const boost::string_ref label, const T &value) + { + m_transcript_builder.append(label, value); + } + + /// access the transcript data + const void* data() const { return m_transcript_builder.data(); } + std::size_t size() const { return m_transcript_builder.size(); } + +//member variables +private: + /// underlying transcript builder + SpTranscriptBuilder m_transcript_builder; +}; + +//// +// SpKDFTranscript +// - build a data string for a key-derivation function +// - mainly intended for short transcripts (~128 bytes or less) with fixed-length and statically ordered components +// - main format: prefix || domain_separator || object1 || object2 || ... +// - simple transcript mode: no labels, flags, or lengths +// note: prefix defaults to "monero" +/// +class SpKDFTranscript final +{ +public: +//constructors + /// normal constructor: start building a transcript with the domain separator + SpKDFTranscript(const boost::string_ref domain_separator, + const std::size_t estimated_data_size, + const boost::string_ref prefix = config::TRANSCRIPT_PREFIX) : + m_transcript_builder{10 + domain_separator.size() + estimated_data_size + prefix.size(), + SpTranscriptBuilder::Mode::SIMPLE} + { + // transcript = prefix || domain_separator + m_transcript_builder.append("", prefix); + m_transcript_builder.append("", domain_separator); + } + +//overloaded operators + /// disable copy/move (this is a scoped manager [of the 'transcript' concept]) + SpKDFTranscript& operator=(SpKDFTranscript&&) = delete; + +//member functions + /// build the transcript + template + void append(const boost::string_ref, const T &value) + { + m_transcript_builder.append("", value); + } + + /// access the transcript data + const void* data() const { return m_transcript_builder.data(); } + std::size_t size() const { return m_transcript_builder.size(); } + +//member variables +private: + /// underlying transcript builder + SpTranscriptBuilder m_transcript_builder; +}; + +} //namespace sp