From d8d0cb17ba23b486a91c2c3782849395a69db47b Mon Sep 17 00:00:00 2001 From: Nik Bougalis Date: Mon, 11 Jul 2016 22:01:41 -0700 Subject: [PATCH] Cryptoconditions Framework (RIPD-1139): Cryptoconditions provide a mechanism to describe a signed message such that multiple actors in a distributed system can all verify the same signed message and agree on whether it matches the description. This provides a useful primitive for event-based systems that are distributed on the Internet since we can describe events in a standard deterministic manner (represented by signed messages) and therefore define generic authenticated event handlers. The cryptoconditions specification implemented is available at: https://tools.ietf.org/html/draft-thomas-crypto-conditions-01 --- Builds/VisualStudio2015/RippleD.vcxproj | 24 ++ .../VisualStudio2015/RippleD.vcxproj.filters | 30 ++ CMakeLists.txt | 3 + SConstruct | 3 + src/ripple/conditions/Condition.h | 175 +++++++++ src/ripple/conditions/Fulfillment.h | 176 +++++++++ src/ripple/conditions/impl/Condition.cpp | 188 +++++++++ src/ripple/conditions/impl/Fulfillment.cpp | 209 ++++++++++ src/ripple/conditions/impl/base64.h | 204 ++++++++++ src/ripple/conditions/impl/utils.h | 368 ++++++++++++++++++ src/ripple/unity/conditions.cpp | 23 ++ src/unity/conditions_test_unity.cpp | 19 + 12 files changed, 1422 insertions(+) create mode 100644 src/ripple/conditions/Condition.h create mode 100644 src/ripple/conditions/Fulfillment.h create mode 100644 src/ripple/conditions/impl/Condition.cpp create mode 100644 src/ripple/conditions/impl/Fulfillment.cpp create mode 100644 src/ripple/conditions/impl/base64.h create mode 100644 src/ripple/conditions/impl/utils.h create mode 100644 src/ripple/unity/conditions.cpp create mode 100644 src/unity/conditions_test_unity.cpp diff --git a/Builds/VisualStudio2015/RippleD.vcxproj b/Builds/VisualStudio2015/RippleD.vcxproj index b12cc027e..5323b32b9 100644 --- a/Builds/VisualStudio2015/RippleD.vcxproj +++ b/Builds/VisualStudio2015/RippleD.vcxproj @@ -1780,6 +1780,22 @@ + + + + + + + + True + True + + + True + True + + + @@ -3326,6 +3342,10 @@ + + True + True + True True @@ -4820,6 +4840,10 @@ True True + + True + True + True True diff --git a/Builds/VisualStudio2015/RippleD.vcxproj.filters b/Builds/VisualStudio2015/RippleD.vcxproj.filters index 38f373d98..3c98ff151 100644 --- a/Builds/VisualStudio2015/RippleD.vcxproj.filters +++ b/Builds/VisualStudio2015/RippleD.vcxproj.filters @@ -193,6 +193,12 @@ {C94B6C51-E253-633B-0AA8-8D18CD695D5E} + + {44E216F9-ACFD-B770-C6C9-BFFAD162566D} + + + {155DC1A3-8A60-BC74-A7E4-1AC1A679FFF9} + {235DCF23-2CF8-4F03-1A54-C159823A7E8D} @@ -2409,6 +2415,24 @@ ripple\beast + + ripple\conditions + + + ripple\conditions + + + ripple\conditions\impl + + + ripple\conditions\impl + + + ripple\conditions\impl + + + ripple\conditions\impl + ripple\core @@ -3939,6 +3963,9 @@ ripple\unity + + ripple\unity + ripple\unity @@ -5508,6 +5535,9 @@ unity + + unity + unity diff --git a/CMakeLists.txt b/CMakeLists.txt index 1cedf93cd..df2efeef1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -180,6 +180,7 @@ app_main.cpp app_misc.cpp app_paths.cpp app_tx.cpp +conditions.cpp core.cpp basics.cpp crypto.cpp @@ -199,6 +200,7 @@ src/unity/ app_test_unity.cpp basics_test_unity.cpp beast_test_unity.cpp +conditions_test_unity.cpp core_test_unity.cpp json_test_unity.cpp ledger_test_unity.cpp @@ -250,6 +252,7 @@ foreach(curdir beast/utility app basics + conditions crypto json ledger diff --git a/SConstruct b/SConstruct index d05667a89..8b50ba0b8 100644 --- a/SConstruct +++ b/SConstruct @@ -924,6 +924,7 @@ def get_classic_sources(toolchain): append_sources(result, *list_sources('src/ripple/beast/utility', '.cpp')) append_sources(result, *list_sources('src/ripple/app', '.cpp')) append_sources(result, *list_sources('src/ripple/basics', '.cpp')) + append_sources(result, *list_sources('src/ripple/conditions', '.cpp')) append_sources(result, *list_sources('src/ripple/crypto', '.cpp')) append_sources(result, *list_sources('src/ripple/json', '.cpp')) append_sources(result, *list_sources('src/ripple/ledger', '.cpp')) @@ -986,6 +987,7 @@ def get_unity_sources(toolchain): 'src/ripple/unity/app_misc.cpp', 'src/ripple/unity/app_paths.cpp', 'src/ripple/unity/app_tx.cpp', + 'src/ripple/unity/conditions.cpp', 'src/ripple/unity/core.cpp', 'src/ripple/unity/basics.cpp', 'src/ripple/unity/crypto.cpp', @@ -1003,6 +1005,7 @@ def get_unity_sources(toolchain): 'src/unity/basics_test_unity.cpp', 'src/unity/beast_test_unity.cpp', 'src/unity/core_test_unity.cpp', + 'src/unity/conditions_test_unity.cpp', 'src/unity/json_test_unity.cpp', 'src/unity/ledger_test_unity.cpp', 'src/unity/overlay_test_unity.cpp', diff --git a/src/ripple/conditions/Condition.h b/src/ripple/conditions/Condition.h new file mode 100644 index 000000000..d26bbe558 --- /dev/null +++ b/src/ripple/conditions/Condition.h @@ -0,0 +1,175 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2016 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef RIPPLE_CONDITIONS_CONDITION_H +#define RIPPLE_CONDITIONS_CONDITION_H + +#include // use Beast implementation +#include +#include +#include +#include +#include + +namespace ripple { +namespace cryptoconditions { + +// NIKB-TODO: These should move to a separate file: +std::uint16_t constexpr condition_hashlock = 0; +std::uint16_t constexpr condition_prefix_sha256 = 1; +std::uint16_t constexpr condition_threshold_sha256 = 2; +std::uint16_t constexpr condition_rsa_sha256 = 3; +std::uint16_t constexpr condition_ed25519 = 4; + +// NIKB-TODO: These should be `enum class : std::uint32_t` +std::uint32_t constexpr feature_sha256 = 1; +std::uint32_t constexpr feature_preimage = 2; +std::uint32_t constexpr feature_prefix = 4; +std::uint32_t constexpr feature_threshold = 8; +std::uint32_t constexpr feature_rsa_pss = 16; +std::uint32_t constexpr feature_ed25519 = 32; + +/** The list of all feature suited defined in the RFC */ +std::uint32_t constexpr definedFeatures = + feature_sha256 | + feature_preimage | + feature_prefix | + feature_threshold | + feature_rsa_pss | + feature_ed25519; + +/** The largest fulfillment supported by this implementation. + + Fulfillments larger than this value cannot be processed + and will not be generated. +*/ +constexpr std::size_t maxSupportedFulfillmentLength = 65535; + +struct Condition +{ + std::uint16_t type; + + /** The maximum length of a fulfillment for this condition. + + While it is possible for a fulfillment to be smaller + it can never be bigger than this. + */ + std::uint16_t maxFulfillmentLength; + + /** The features suites required to process a fulfillment. */ + std::uint32_t featureBitmask; + + /** An identifier for this condition. + + This fingerprint is meant to be unique only with + respect to other conditions of the same type. + */ + std::array fingerprint; + + // Can this be deleted? + Condition () = default; + + Condition (Condition const&) = default; + Condition (Condition&&) = default; +}; + +inline +bool +operator== (Condition const& lhs, Condition const& rhs) +{ + return + lhs.type == rhs.type && + lhs.featureBitmask == rhs.featureBitmask && + lhs.maxFulfillmentLength == rhs.maxFulfillmentLength && + lhs.fingerprint == rhs.fingerprint; +} + +inline +bool +operator!= (Condition const& lhs, Condition const& rhs) +{ + return !(lhs == rhs); +} + +/** Determine if a given condition is valid. + + @note this function checks whether it understands the + type of the condition, and if so, whether it meets + the requirements mandated by the RFC. +*/ +inline +bool +validate (Condition const& c) +{ + // This check can never trigger because of the range of + // the maxFulfillmentLength type. It's here in case the + // type is changed in the future. + + if (c.maxFulfillmentLength > maxSupportedFulfillmentLength) + return false; + + return false; +} + +/** True if condition type is specified in the RFC. + + @note: this function may return true even if the type + of condition isn't presently supported by this + implementation. +*/ +inline +bool +isCondition (std::uint16_t type) +{ + switch(type) + { + case condition_hashlock: + case condition_prefix_sha256: + case condition_threshold_sha256: + case condition_rsa_sha256: + case condition_ed25519: + return true; + + default: + return false; + } +} + +/** Load a serialized condition either from its string or binary form */ +/** @{ */ +boost::optional +loadCondition(std::string const& s); + +boost::optional +loadCondition(Slice s); +/** @} */ + +// Convert a condition to its string form +std::string +to_string (Condition const& c); + +// Convert a condition to its binary form +std::vector +to_blob (Condition const& c); + +} + +} + +#endif diff --git a/src/ripple/conditions/Fulfillment.h b/src/ripple/conditions/Fulfillment.h new file mode 100644 index 000000000..e1657c5ae --- /dev/null +++ b/src/ripple/conditions/Fulfillment.h @@ -0,0 +1,176 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2016 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef RIPPLE_CONDITIONS_FULFILLMENT_H +#define RIPPLE_CONDITIONS_FULFILLMENT_H + +#include +#include +#include +#include +#include + +namespace ripple { +namespace cryptoconditions { + +struct Fulfillment +{ +public: + virtual ~Fulfillment() = default; + + Fulfillment () = default; + + /** Returns the size of the fulfillment's payload. */ + virtual + std::size_t + payloadSize() const = 0; + + /** Returns the fulfillment's payload */ + virtual + Buffer + payload() const = 0; + + /** Generates the condition */ + virtual + Condition + condition() const = 0; + + /** Returns the type */ + virtual + std::uint16_t + type () const = 0; + + /** Returns the features suites required. + + For any given fulfillment, the result includes all + the feature suites that an implementation must + support in order to be able to successfully parse + the fulfillment. + + @note fulfillments of the same type may require + different features. + */ + virtual + std::uint32_t + features () const = 0; + + /** Determines if this fulfillment is well-formed */ + virtual + bool + ok () const = 0; + + /** Validates a fulfillment. */ + virtual + bool + validate (Slice data) const = 0; + + /** Parses the fulfillment's payload. */ + virtual + bool + parsePayload (Slice s) = 0; +}; + +inline +bool +operator== (Fulfillment const& lhs, Fulfillment const& rhs) +{ + return + lhs.type() == rhs.type() && + lhs.ok() == rhs.ok() && + lhs.payload() == rhs.payload(); +} + +inline +bool +operator!= (Fulfillment const& lhs, Fulfillment const& rhs) +{ + return !(lhs == rhs); +} + +/** Load a fulfillment from its string serialization. + + The format is specified in Section 2.5.1 of the + cryptoconditions RFC: + + https://tools.ietf.org/html/draft-thomas-crypto-conditions-00#section-2.5.1 + */ +std::unique_ptr +loadFulfillment (std::string const& s); + +/** Load a fulfillment from its binary serialization. + + The format is specified in Section 2.5.2 of the + cryptoconditions RFC: + + https://tools.ietf.org/html/draft-thomas-crypto-conditions-00#section-2.5.2 +*/ +std::unique_ptr +loadFulfillment (Slice s); + +// Convert a fulfillment to its string form +std::string +to_string (Fulfillment const& f); + +// Convert a fulfillment to its binary form +std::vector +to_blob (Fulfillment const& f); + +/** Determine whether a fulfillment fulfills a given condition */ +bool +fulfills ( + Fulfillment const& f, + Condition const& c); + +/** Verify if the given message satisfies the fulfillment. + + @param f The fulfillment + @param c The condition + @param m The message; note that the message is not + relevant for some conditions (e.g. hashlocks) + and a fulfillment will successfully satisfy its + condition for any given message. +*/ +bool +validate ( + Fulfillment const& f, + Condition const& c, + Slice m); + +/** Verify a cryptoconditional trigger. + + A cryptoconditional trigger is a cryptocondition with + an empty message. + + When using such triggers, it is recommended that the + trigger be of type preimage, prefix or threshold. If + a signature type is used (i.e. Ed25519 or RSA-SHA256) + then the Ed25519 or RSA keys should be single-use keys. + + @param f The fulfillment + @param c The condition +*/ +bool +validateTrigger ( + Fulfillment const& f, + Condition const& c); + +} +} + +#endif diff --git a/src/ripple/conditions/impl/Condition.cpp b/src/ripple/conditions/impl/Condition.cpp new file mode 100644 index 000000000..72bbee835 --- /dev/null +++ b/src/ripple/conditions/impl/Condition.cpp @@ -0,0 +1,188 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2016 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace ripple { +namespace cryptoconditions { + +boost::optional +loadCondition(std::string const& s) +{ + static boost::regex const re_current ( + "^" // start of line + "cc:" // 'cc' for cryptocondition + "([1-9a-f][0-9a-f]{0,3}|0):" // type (hexadecimal) + "([1-9a-f][0-9a-f]{0,15}):" // feature bitmask (hexadecimal) + "([a-zA-Z0-9_-]{0,86}):" // fingerprint (base64url) + "([1-9][0-9]{0,17}|0)" // fulfillment length (decimal) + "$" // end of line + , boost::regex_constants::optimize + ); + + boost::smatch match; + + if (!boost::regex_match (s, match, re_current)) + return boost::none; + + try + { + Condition c; + + c.type = parse_hexadecimal (match[1]); + + if (!isCondition (c.type)) + return boost::none; + + c.featureBitmask = parse_hexadecimal(match[2]); + c.maxFulfillmentLength = parse_decimal(match[4]); + + if (c.maxFulfillmentLength > maxSupportedFulfillmentLength) + return boost::none; + + // TODO: Avoid copying by decoding directly + // into the condition's buffer + auto fingerprint = base64url_decode(match[3]); + + if (fingerprint.size() != c.fingerprint.size()) + return boost::none; + + std::memcpy( + c.fingerprint.data(), + fingerprint.data(), + fingerprint.size()); + + return c; + } + catch (std::exception const&) + { + return boost::none; + } +} + +boost::optional +loadCondition(Slice s) +{ + if (s.empty()) + return boost::none; + + try + { + auto start = s.data(); + auto finish = s.data() + s.size(); + + Condition c; + + std::tie (start, c.type) = + oer::decode_integer ( + start, finish); + + if (!isCondition (c.type)) + return boost::none; + + std::tie (start, c.featureBitmask) = + oer::decode_varuint ( + start, finish); + + { + std::size_t len; + + std::tie (start, len) = + oer::decode_length (start, finish); + + // Incorrect signature length + if (len != c.fingerprint.size()) + return boost::none; + + // Short buffer + if (std::distance (start, finish) < len) + return boost::none; + + auto p = c.fingerprint.data(); + + while (len--) + *p++ = *start++; + } + + if (start == finish) + return boost::none; + + std::tie (start, c.maxFulfillmentLength) = + oer::decode_varuint ( + start, finish); + + // The maximum supported length of a fulfillment is + // the largest allowable value, so checking here is + // not helpful. + return c; + } + catch (std::exception const&) + { + return boost::none; + } +} + +std::string +to_string (Condition const& c) +{ + return std::string("cc:") + + to_hex (c.type) + ":" + + to_hex (c.featureBitmask) + ":" + + base64url_encode(c.fingerprint) + ":" + + to_dec (c.maxFulfillmentLength); +} + +std::vector +to_blob (Condition const& c) +{ + // TODO: optimize this + std::vector v; + v.reserve (48); + + oer::encode_integer ( + c.type, + std::back_inserter(v)); + + oer::encode_varuint ( + c.featureBitmask, + std::back_inserter(v)); + + oer::encode_octetstring ( + c.fingerprint.size(), + c.fingerprint.begin(), + c.fingerprint.end(), + std::back_inserter(v)); + + oer::encode_varuint ( + c.maxFulfillmentLength, + std::back_inserter(v)); + + return v; +} + +} + +} diff --git a/src/ripple/conditions/impl/Fulfillment.cpp b/src/ripple/conditions/impl/Fulfillment.cpp new file mode 100644 index 000000000..318f4d610 --- /dev/null +++ b/src/ripple/conditions/impl/Fulfillment.cpp @@ -0,0 +1,209 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2016 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include +#include +#include +#include +#include +#include +#include + +namespace ripple { +namespace cryptoconditions { + +bool +fulfills ( + Fulfillment const& f, + Condition const& c) +{ + // Fast check: the fulfillment's type must match the + // conditions's type: + if (f.type() != c.type) + return false; + + // Ensure that the condition is well-formed + if (!validate (c)) + return false; + + // The fulfillment payload can be no larger than the + // what the condition allows. + if (f.payloadSize() > c.maxFulfillmentLength) + return false; + + return f.condition() == c; +} + +bool +validate ( + Fulfillment const& f, + Condition const& c, + Slice m) +{ + return fulfills (f, c) && f.validate (m); +} + +bool +validateTrigger ( + Fulfillment const& f, + Condition const& c) +{ + return validate (f, c, {}); +} + +std::unique_ptr +loadFulfillment (std::uint16_t type, Slice payload) +{ + std::unique_ptr p; + + switch (type) + { + default: + throw std::domain_error ( + "Unknown cryptocondition type " + + std::to_string (type)); + } + + // If the payload can't be parsed, the load should + // fail. + if (p && !p->parsePayload(payload)) + p.reset(); + + return p; +} + +// Parse a condition from its string form +std::unique_ptr +loadFulfillment (std::string const& s) +{ + // CHECKME: custom parser maybe? probably faster but + // more work and probability of error. + + // TODO: use two regex: one that accepts anything the + // standard supports and one which accepts only what + // we support. Parse with both for improved errors? + static boost::regex const re_current ( + "^" // start of line + "cf:" // 'cf' for fulfillment + "([1-9a-f][0-9a-f]{0,3}|0):" // type + "([a-zA-Z0-9_-]*)" // fulfillment payload (base64url) + "$" // end of line + , boost::regex_constants::optimize + ); + + try + { + boost::smatch match; + + if (!boost::regex_match (s, match, re_current)) + return nullptr; + + std::uint16_t const type = + parse_hexadecimal(match[1]); + + auto payload = base64url_decode (match[2]); + + if (payload.size() > maxSupportedFulfillmentLength) + return nullptr; + + return loadFulfillment (type, makeSlice (payload)); + } + catch (std::exception const&) + { + return nullptr; + } +} + +std::unique_ptr +loadFulfillment (Slice s) +{ + if (s.empty()) + return nullptr; + + try + { + auto start = s.data(); + auto finish = s.data() + s.size(); + + std::uint16_t type; + std::size_t len; + + std::tie (start, type) = + oer::decode_integer ( + start, finish); + + if (!isCondition (type)) + return nullptr; + + if (start == finish) + return nullptr; + + std::tie (start, len) = + oer::decode_length( + start, finish); + + if (len) + { + if (len > maxSupportedFulfillmentLength) + return nullptr; + + if (std::distance (start, finish) < len) + return nullptr; + } + + return loadFulfillment (type, Slice{ start, len }); + } + catch (std::exception const&) + { + return nullptr; + } +} + +std::string +to_string (Fulfillment const& f) +{ + return std::string("cf:") + to_hex(f.type()) + + ":" + base64url_encode (f.payload()); +} + +std::vector +to_blob (Fulfillment const& f) +{ + // NIKB TODO optimize this + std::vector v; + + auto const p = f.payload(); + + oer::encode_integer ( + f.type(), + std::back_inserter(v)); + + oer::encode_length ( + p.size(), std::back_inserter(v)); + + oer::encode_octetstring ( + p.data(), + p.data() + p.size(), + std::back_inserter(v)); + + return v; +} + +} +} diff --git a/src/ripple/conditions/impl/base64.h b/src/ripple/conditions/impl/base64.h new file mode 100644 index 000000000..8e8edc0ed --- /dev/null +++ b/src/ripple/conditions/impl/base64.h @@ -0,0 +1,204 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_DETAIL_BASE64_HPP +#define BEAST_DETAIL_BASE64_HPP + +#include +#include +#include +#include +#include +#include +#include + +/* + Portions from http://www.adp-gmbh.ch/cpp/common/base64.html + Copyright notice: + + base64.cpp and base64.h + + Copyright (C) 2004-2008 René Nyffenegger + + This source code is provided 'as-is', without any express or implied + warranty. In no event will the author be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this source code must not be misrepresented; you must not + claim that you wrote the original source code. If you use this source code + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original source code. + + 3. This notice may not be removed or altered from any source distribution. + + René Nyffenegger rene.nyffenegger@adp-gmbh.ch + +*/ + +namespace ripple { +namespace cryptoconditions { + +// NIKB NOTE: This has *NOT* standard base64 - it's base64url, +// which replaces the `+` with a `-` and the the `/` with a `_` +// with the padding suppressed on encoding and rejected on +// decoding. + +template +std::string const& +base64url_alphabet() +{ + static std::string const alphabet = + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789-_"; + return alphabet; +} + +inline +bool +is_base64url(unsigned char c) +{ + return (std::isalnum(c) || (c == '-') || (c == '_')); +} + +template +std::string +base64url_encode (std::uint8_t const* data, + std::size_t in_len) +{ + unsigned char c3[3], c4[4]; + int i = 0; + int j = 0; + + std::string ret; + ret.reserve (3 + in_len * 8 / 6); + + char const* alphabet (base64url_alphabet().data()); + + while(in_len--) + { + c3[i++] = *(data++); + if(i == 3) + { + c4[0] = (c3[0] & 0xfc) >> 2; + c4[1] = ((c3[0] & 0x03) << 4) + ((c3[1] & 0xf0) >> 4); + c4[2] = ((c3[1] & 0x0f) << 2) + ((c3[2] & 0xc0) >> 6); + c4[3] = c3[2] & 0x3f; + for(i = 0; (i < 4); i++) + ret += alphabet[c4[i]]; + i = 0; + } + } + + if(i) + { + for(j = i; j < 3; j++) + c3[j] = '\0'; + + c4[0] = (c3[0] & 0xfc) >> 2; + c4[1] = ((c3[0] & 0x03) << 4) + ((c3[1] & 0xf0) >> 4); + c4[2] = ((c3[1] & 0x0f) << 2) + ((c3[2] & 0xc0) >> 6); + c4[3] = c3[2] & 0x3f; + + for(j = 0; (j < i + 1); j++) + ret += alphabet[c4[j]]; + } + + return ret; + +} + +template +std::string +base64url_encode (std::array const& d) +{ + return base64url_encode (d.data(), d.size()); +} + +template +std::string +base64url_encode (std::vector const& d) +{ + return base64url_encode (d.data(), d.size()); +} + +template +std::string +base64url_encode (Buffer const& d) +{ + return base64url_encode (d.data(), d.size()); +} + +template +std::string +base64url_encode (Slice d) +{ + return base64url_encode (d.data(), d.size()); +} + +template +std::vector +base64url_decode(std::string const& data) +{ + int in_len = data.size(); + std::uint8_t c4[4]; + int i = 0; + int j = 0; + int in_ = 0; + + std::vector ret; + ret.reserve (in_len * 6 / 8); + + while(in_len-- && is_base64url(data[in_])) + { + c4[i++] = data[in_]; in_++; + if(i == 4) { + for(i = 0; i < 4; i++) + c4[i] = static_cast( + base64url_alphabet().find(c4[i])); + + ret.push_back ((c4[0] << 2) + ((c4[1] & 0x30) >> 4)); + ret.push_back (((c4[1] & 0xf) << 4) + ((c4[2] & 0x3c) >> 2)); + ret.push_back (((c4[2] & 0x3) << 6) + c4[3]); + + i = 0; + } + } + + if(i) + { + for(j = i; j < 4; j++) + c4[j] = 0; + + for(j = 0; j < 4; j++) + c4[j] = static_cast( + base64url_alphabet().find(c4[j])); + + std::uint8_t c3[3]; + + c3[0] = (c4[0] << 2) + ((c4[1] & 0x30) >> 4); + c3[1] = ((c4[1] & 0xf) << 4) + ((c4[2] & 0x3c) >> 2); + c3[2] = ((c4[2] & 0x3) << 6) + c4[3]; + + for(j = 0; (j < i - 1); j++) + ret.push_back (c3[j]); + } + + return ret; +} + +} +} + +#endif diff --git a/src/ripple/conditions/impl/utils.h b/src/ripple/conditions/impl/utils.h new file mode 100644 index 000000000..b59ad312b --- /dev/null +++ b/src/ripple/conditions/impl/utils.h @@ -0,0 +1,368 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2016 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef RIPPLE_CONDITIONS_UTILS_H +#define RIPPLE_CONDITIONS_UTILS_H + +#include +#include +#include +#include +#include +#include +#include + +namespace ripple { +namespace cryptoconditions { + +inline +std::string +hexstr (std::vector const& data) +{ + std::string s; + s.reserve (data.size() * 2); + + for (auto d : data) + { + s.push_back (charHex (d >> 4)); + s.push_back (charHex (d & 15)); + } + + return s; +} + +inline +std::vector +hexblob (std::string const& s) +{ + std::vector result; + result.reserve (1 + (s.size () / 2)); + + auto iter = s.cbegin (); + + if (s.size () & 1) + { + int c = charUnHex (*iter++); + + if (c < 0) + Throw("Invalid hex in blob"); + + result.push_back(c); + } + + while (iter != s.cend ()) + { + int cHigh = charUnHex (*iter++); + + if (cHigh < 0) + Throw("Invalid hex in blob"); + + int cLow = charUnHex (*iter); + + if (cLow < 0) + Throw("Invalid hex in blob"); + + iter++; + + result.push_back ( + static_cast(cHigh << 4) | + static_cast(cLow)); + } + + return result; +} + +template +T parse_decimal(std::string const& s) +{ + T t = 0; + + for (auto const c : s) + { + if (c < '0' || c > '9') + throw std::domain_error ("invalid decimal digit"); + + t = (t * 10) + (c - '0'); + } + + return t; +} + +template +T parse_hexadecimal(std::string const& s) +{ + T t = 0; + + for (auto const c : s) + { + if (c >= '0' && c <= '9') + t = (t * 16) + (c - '0'); + else if (c >= 'a' && c <= 'f') + t = (t * 16) + 10 + (c - 'a'); + else if (c >= 'A' && c <= 'F') + t = (t * 16) + 10 + (c - 'A'); + else + throw std::domain_error ("invalid hexadecimal digit"); + } + + return t; +} + +template +std::string +to_hex (Integer value) +{ + std::stringstream ss; + ss << std::hex << value; + return ss.str(); +} + +template +std::string +to_dec (Integer value) +{ + std::stringstream ss; + ss << std::dec << value; + return ss.str(); +} + +// ISO/IEC 8825/7 or ITU-T X.696: Octet Encoding Rules +// FIXME: This assumes a little-endian architecture! +namespace oer +{ + +// Simple conversion: write integer as big-endian byte stream +// This needs to be improved and optimized: +template +void +encode_integer(Integer value, OutputIt out) +{ + static_assert ( + std::is_same::value || + std::is_same::value || + std::is_same::value || + std::is_same::value, + "encode_integer accepts only std::uint{8,16,32,64}_t"); + + std::size_t n = sizeof(Integer); + + while(n--) + { + *out++ = static_cast( + (value >> (n * 8)) & 0xFF); + } +} + +// Simple conversion: big-endian byte stream to integer +template +std::pair +decode_integer(InputIt begin, InputIt end) +{ + static_assert ( + std::is_same::value || + std::is_same::value || + std::is_same::value || + std::is_same::value, + "decode_integer accepts only std::uint{8,16,32,64}_t"); + + std::size_t size = std::distance (begin, end); + + if (size < sizeof(Integer)) + Throw("short integer: " + std::to_string(size)); + + Integer res = 0; + + for (std::size_t i = 0; i < sizeof(Integer); ++i) + res = (res << 8) | *begin++; + + return { begin, res }; +} + +template +inline +OutputIt +encode_length (std::size_t len, OutputIt out) +{ + if (len <= 0x7F) + { + *out++ = static_cast(len & 0x7F); + return out; + } + + // Decide how many bytes we need: + if (len <= 0xFFFF) + { + *out++ = 0x82; + *out++ = static_cast((len >> 8) & 0xFF); + *out++ = static_cast(len & 0xFF); + return out; + } + + if (len <= 0xFFFFFF) + { + *out++ = 0x83; + *out++ = static_cast((len >> 16) & 0xFF); + *out++ = static_cast((len >> 8) & 0xFF); + *out++ = static_cast(len & 0xFF); + return out; + } + + if (len <= 0xFFFFFFFF) + { + *out++ = 0x84; + *out++ = static_cast((len >> 24) & 0xFF); + *out++ = static_cast((len >> 16) & 0xFF); + *out++ = static_cast((len >> 8) & 0xFF); + *out++ = static_cast(len & 0xFF); + return out; + } + + // Note: OER can represent lengths up to (2^1016) - 1, + // which is, truly, enough for everyone. We never + // exceed 2^32. + Throw("overlong encoding length: " + std::to_string(len)); +} + +// A "streambuf" would serve us better here - instead of the +// crazy paired return, we consume data from it and things +// just magically work. +template +std::pair +decode_length (InputIt begin, InputIt end) +{ + if (begin == end) + Throw("empty buffer"); + + std::size_t bytes = *begin++; + + if (bytes < 128) + return { begin, bytes }; + + bytes &= 0x7F; + + if (bytes > 4) + Throw("overlong encoded length: " + std::to_string(bytes)); + + std::size_t len = 0; + + if (std::distance (begin, end) < bytes) + Throw("short encoded length: " + std::to_string(bytes)); + + while (bytes--) + len = (len << 8) | *begin++; + + return { begin, len }; +} + +/** Encode a fixed-size octet string: OER 2.6 (2) */ +template +OutputIt +encode_octetstring(InputIt begin, InputIt end, OutputIt out) +{ + while (begin != end) + *out++ = *begin++; + + return out; +} + +/** Encode a dynamic size octet string: OER 2.6 (1) */ +inline +std::size_t +predict_octetstring_size(std::size_t size) +{ + // Alternatively, always guess 4 + size and call it a day? + if (size <= 0x7F) + return size + 1; + + // Decide how many bytes we need: + if (size <= 0xFFFF) + return size + 3; + + if (size <= 0xFFFFFF) + return size + 4; + + if (size <= 0xFFFFFFFF) + return size + 5; + + Throw("overlong encoding length: " + std::to_string(size)); +} + +/** Encode an dynamic size octet string: OER 2.6 (1) */ +template +OutputIt +encode_octetstring(std::size_t size, InputIt begin, InputIt end, OutputIt out) +{ + // This will encode the length first, followed by the + // payload octets: + return encode_octetstring ( + begin, + end, + oer::encode_length (size, out)); +} + +template +std::enable_if_t::value> +encode_varuint (Integer value, OutputIt out) +{ + auto count = [](Integer n) + { + std::size_t c = 0; + + do + { + n >>= 8; + ++c; + } while (n); + + return c; + }; + + std::size_t c = count (value); + + out = encode_length (c, out); + + while(c--) + { + *out++ = static_cast( + (value >> (c * 8)) & 0xFF); + } +} + +template +std::enable_if_t::value, std::pair> +decode_varuint (InputIt begin, InputIt end) +{ + auto y = decode_length (begin, end); + + if (y.second > sizeof(Integer)) + Throw("Encoded integer exceeds allowable range: " + std::to_string(y.second)); + + Integer x = 0; + + for (std::size_t i = 0; i != y.second; ++i) + x = (x << 8) + *y.first++; + + return { y.first, x }; +} + +} +} +} + +#endif diff --git a/src/ripple/unity/conditions.cpp b/src/ripple/unity/conditions.cpp new file mode 100644 index 000000000..41bb21824 --- /dev/null +++ b/src/ripple/unity/conditions.cpp @@ -0,0 +1,23 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2012, 2013 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include + +#include +#include diff --git a/src/unity/conditions_test_unity.cpp b/src/unity/conditions_test_unity.cpp new file mode 100644 index 000000000..398cc1921 --- /dev/null +++ b/src/unity/conditions_test_unity.cpp @@ -0,0 +1,19 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2012, 2013 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== +