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
This commit is contained in:
Nik Bougalis
2016-07-11 22:01:41 -07:00
parent 47a919faf0
commit d8d0cb17ba
12 changed files with 1422 additions and 0 deletions

View File

@@ -1780,6 +1780,22 @@
</ClInclude>
<ClInclude Include="..\..\src\ripple\beast\xor_shift_engine.h">
</ClInclude>
<ClInclude Include="..\..\src\ripple\conditions\Condition.h">
</ClInclude>
<ClInclude Include="..\..\src\ripple\conditions\Fulfillment.h">
</ClInclude>
<ClInclude Include="..\..\src\ripple\conditions\impl\base64.h">
</ClInclude>
<ClCompile Include="..\..\src\ripple\conditions\impl\Condition.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\src\ripple\conditions\impl\Fulfillment.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
</ClCompile>
<ClInclude Include="..\..\src\ripple\conditions\impl\utils.h">
</ClInclude>
<ClInclude Include="..\..\src\ripple\core\Config.h">
</ClInclude>
<ClInclude Include="..\..\src\ripple\core\ConfigSections.h">
@@ -3326,6 +3342,10 @@
</ClCompile>
<ClCompile Include="..\..\src\ripple\unity\beast.cpp">
</ClCompile>
<ClCompile Include="..\..\src\ripple\unity\conditions.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug.classic|x64'">True</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release.classic|x64'">True</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\src\ripple\unity\core.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug.classic|x64'">True</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release.classic|x64'">True</ExcludedFromBuild>
@@ -4820,6 +4840,10 @@
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug.classic|x64'">True</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release.classic|x64'">True</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\src\unity\conditions_test_unity.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug.classic|x64'">True</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release.classic|x64'">True</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\src\unity\core_test_unity.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug.classic|x64'">True</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release.classic|x64'">True</ExcludedFromBuild>

View File

@@ -193,6 +193,12 @@
<Filter Include="ripple\beast\utility\src">
<UniqueIdentifier>{C94B6C51-E253-633B-0AA8-8D18CD695D5E}</UniqueIdentifier>
</Filter>
<Filter Include="ripple\conditions">
<UniqueIdentifier>{44E216F9-ACFD-B770-C6C9-BFFAD162566D}</UniqueIdentifier>
</Filter>
<Filter Include="ripple\conditions\impl">
<UniqueIdentifier>{155DC1A3-8A60-BC74-A7E4-1AC1A679FFF9}</UniqueIdentifier>
</Filter>
<Filter Include="ripple\core">
<UniqueIdentifier>{235DCF23-2CF8-4F03-1A54-C159823A7E8D}</UniqueIdentifier>
</Filter>
@@ -2409,6 +2415,24 @@
<ClInclude Include="..\..\src\ripple\beast\xor_shift_engine.h">
<Filter>ripple\beast</Filter>
</ClInclude>
<ClInclude Include="..\..\src\ripple\conditions\Condition.h">
<Filter>ripple\conditions</Filter>
</ClInclude>
<ClInclude Include="..\..\src\ripple\conditions\Fulfillment.h">
<Filter>ripple\conditions</Filter>
</ClInclude>
<ClInclude Include="..\..\src\ripple\conditions\impl\base64.h">
<Filter>ripple\conditions\impl</Filter>
</ClInclude>
<ClCompile Include="..\..\src\ripple\conditions\impl\Condition.cpp">
<Filter>ripple\conditions\impl</Filter>
</ClCompile>
<ClCompile Include="..\..\src\ripple\conditions\impl\Fulfillment.cpp">
<Filter>ripple\conditions\impl</Filter>
</ClCompile>
<ClInclude Include="..\..\src\ripple\conditions\impl\utils.h">
<Filter>ripple\conditions\impl</Filter>
</ClInclude>
<ClInclude Include="..\..\src\ripple\core\Config.h">
<Filter>ripple\core</Filter>
</ClInclude>
@@ -3939,6 +3963,9 @@
<ClCompile Include="..\..\src\ripple\unity\beast.cpp">
<Filter>ripple\unity</Filter>
</ClCompile>
<ClCompile Include="..\..\src\ripple\unity\conditions.cpp">
<Filter>ripple\unity</Filter>
</ClCompile>
<ClCompile Include="..\..\src\ripple\unity\core.cpp">
<Filter>ripple\unity</Filter>
</ClCompile>
@@ -5508,6 +5535,9 @@
<ClCompile Include="..\..\src\unity\beast_test_unity.cpp">
<Filter>unity</Filter>
</ClCompile>
<ClCompile Include="..\..\src\unity\conditions_test_unity.cpp">
<Filter>unity</Filter>
</ClCompile>
<ClCompile Include="..\..\src\unity\core_test_unity.cpp">
<Filter>unity</Filter>
</ClCompile>

View File

@@ -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

View File

@@ -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',

View File

@@ -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 <ripple/conditions/impl/base64.h> // use Beast implementation
#include <ripple/conditions/impl/utils.h>
#include <boost/optional.hpp>
#include <array>
#include <cstdint>
#include <string>
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<std::uint8_t, 32> 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<Condition>
loadCondition(std::string const& s);
boost::optional<Condition>
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<std::uint8_t>
to_blob (Condition const& c);
}
}
#endif

View File

@@ -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 <ripple/basics/Buffer.h>
#include <ripple/basics/Slice.h>
#include <ripple/conditions/Condition.h>
#include <ripple/conditions/impl/utils.h>
#include <boost/optional.hpp>
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<Fulfillment>
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<Fulfillment>
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<std::uint8_t>
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

View File

@@ -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 <ripple/basics/contract.h>
#include <ripple/conditions/Condition.h>
#include <ripple/conditions/Fulfillment.h>
#include <ripple/conditions/impl/utils.h>
#include <boost/regex.hpp>
#include <boost/optional.hpp>
#include <vector>
#include <iostream>
namespace ripple {
namespace cryptoconditions {
boost::optional<Condition>
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<std::uint16_t> (match[1]);
if (!isCondition (c.type))
return boost::none;
c.featureBitmask = parse_hexadecimal<std::uint32_t>(match[2]);
c.maxFulfillmentLength = parse_decimal<std::uint16_t>(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<Condition>
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<std::uint16_t> (
start, finish);
if (!isCondition (c.type))
return boost::none;
std::tie (start, c.featureBitmask) =
oer::decode_varuint<std::uint32_t> (
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<std::uint16_t> (
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<std::uint8_t>
to_blob (Condition const& c)
{
// TODO: optimize this
std::vector<std::uint8_t> 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;
}
}
}

View File

@@ -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 <ripple/conditions/Condition.h>
#include <ripple/conditions/Fulfillment.h>
#include <ripple/conditions/impl/utils.h>
#include <boost/regex.hpp>
#include <boost/optional.hpp>
#include <type_traits>
#include <vector>
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<Fulfillment>
loadFulfillment (std::uint16_t type, Slice payload)
{
std::unique_ptr<Fulfillment> 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<Fulfillment>
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<std::uint16_t>(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<Fulfillment>
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<std::uint16_t> (
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<std::uint8_t>
to_blob (Fulfillment const& f)
{
// NIKB TODO optimize this
std::vector<std::uint8_t> 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;
}
}
}

View File

@@ -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 <ripple/basics/Buffer.h>
#include <ripple/basics/Slice.h>
#include <array>
#include <cctype>
#include <string>
#include <cstdint>
#include <vector>
/*
Portions from http://www.adp-gmbh.ch/cpp/common/base64.html
Copyright notice:
base64.cpp and base64.h
Copyright (C) 2004-2008 Ren<65> 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<65> 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 <class = void>
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 <class = void>
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 <size_t N>
std::string
base64url_encode (std::array<std::uint8_t, N> const& d)
{
return base64url_encode (d.data(), d.size());
}
template <class = void>
std::string
base64url_encode (std::vector<std::uint8_t> const& d)
{
return base64url_encode (d.data(), d.size());
}
template <class = void>
std::string
base64url_encode (Buffer const& d)
{
return base64url_encode (d.data(), d.size());
}
template <class = void>
std::string
base64url_encode (Slice d)
{
return base64url_encode (d.data(), d.size());
}
template <class = void>
std::vector<std::uint8_t>
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<std::uint8_t> 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<unsigned char>(
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<unsigned char>(
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

View File

@@ -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 <ripple/basics/strHex.h>
#include <stdexcept>
#include <string>
#include <vector>
#include <iomanip>
#include <sstream>
#include <utility>
namespace ripple {
namespace cryptoconditions {
inline
std::string
hexstr (std::vector<std::uint8_t> 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<std::uint8_t>
hexblob (std::string const& s)
{
std::vector<std::uint8_t> result;
result.reserve (1 + (s.size () / 2));
auto iter = s.cbegin ();
if (s.size () & 1)
{
int c = charUnHex (*iter++);
if (c < 0)
Throw<std::runtime_error>("Invalid hex in blob");
result.push_back(c);
}
while (iter != s.cend ())
{
int cHigh = charUnHex (*iter++);
if (cHigh < 0)
Throw<std::runtime_error>("Invalid hex in blob");
int cLow = charUnHex (*iter);
if (cLow < 0)
Throw<std::runtime_error>("Invalid hex in blob");
iter++;
result.push_back (
static_cast<std::uint8_t>(cHigh << 4) |
static_cast<std::uint8_t>(cLow));
}
return result;
}
template <class T>
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 <class T>
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 <class Integer>
std::string
to_hex (Integer value)
{
std::stringstream ss;
ss << std::hex << value;
return ss.str();
}
template <class Integer>
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 <class Integer, class OutputIt>
void
encode_integer(Integer value, OutputIt out)
{
static_assert (
std::is_same<Integer, std::uint8_t>::value ||
std::is_same<Integer, std::uint16_t>::value ||
std::is_same<Integer, std::uint32_t>::value ||
std::is_same<Integer, std::uint64_t>::value,
"encode_integer accepts only std::uint{8,16,32,64}_t");
std::size_t n = sizeof(Integer);
while(n--)
{
*out++ = static_cast<std::uint8_t>(
(value >> (n * 8)) & 0xFF);
}
}
// Simple conversion: big-endian byte stream to integer
template <class Integer, class InputIt>
std::pair<InputIt, Integer>
decode_integer(InputIt begin, InputIt end)
{
static_assert (
std::is_same<Integer, std::uint8_t>::value ||
std::is_same<Integer, std::uint16_t>::value ||
std::is_same<Integer, std::uint32_t>::value ||
std::is_same<Integer, std::uint64_t>::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<std::length_error>("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 <class OutputIt>
inline
OutputIt
encode_length (std::size_t len, OutputIt out)
{
if (len <= 0x7F)
{
*out++ = static_cast<std::uint8_t>(len & 0x7F);
return out;
}
// Decide how many bytes we need:
if (len <= 0xFFFF)
{
*out++ = 0x82;
*out++ = static_cast<std::uint8_t>((len >> 8) & 0xFF);
*out++ = static_cast<std::uint8_t>(len & 0xFF);
return out;
}
if (len <= 0xFFFFFF)
{
*out++ = 0x83;
*out++ = static_cast<std::uint8_t>((len >> 16) & 0xFF);
*out++ = static_cast<std::uint8_t>((len >> 8) & 0xFF);
*out++ = static_cast<std::uint8_t>(len & 0xFF);
return out;
}
if (len <= 0xFFFFFFFF)
{
*out++ = 0x84;
*out++ = static_cast<std::uint8_t>((len >> 24) & 0xFF);
*out++ = static_cast<std::uint8_t>((len >> 16) & 0xFF);
*out++ = static_cast<std::uint8_t>((len >> 8) & 0xFF);
*out++ = static_cast<std::uint8_t>(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<std::length_error>("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 <class InputIt>
std::pair<InputIt, std::size_t>
decode_length (InputIt begin, InputIt end)
{
if (begin == end)
Throw<std::length_error>("empty buffer");
std::size_t bytes = *begin++;
if (bytes < 128)
return { begin, bytes };
bytes &= 0x7F;
if (bytes > 4)
Throw<std::length_error>("overlong encoded length: " + std::to_string(bytes));
std::size_t len = 0;
if (std::distance (begin, end) < bytes)
Throw<std::length_error>("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 <class InputIt, class OutputIt>
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<std::length_error>("overlong encoding length: " + std::to_string(size));
}
/** Encode an dynamic size octet string: OER 2.6 (1) */
template <class InputIt, class OutputIt>
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 <class Integer, class OutputIt>
std::enable_if_t<std::is_unsigned<Integer>::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<std::uint8_t>(
(value >> (c * 8)) & 0xFF);
}
}
template <class Integer, class InputIt>
std::enable_if_t<std::is_unsigned<Integer>::value, std::pair<InputIt, Integer>>
decode_varuint (InputIt begin, InputIt end)
{
auto y = decode_length (begin, end);
if (y.second > sizeof(Integer))
Throw<std::length_error>("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

View File

@@ -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 <BeastConfig.h>
#include <ripple/conditions/impl/Condition.cpp>
#include <ripple/conditions/impl/Fulfillment.cpp>

View File

@@ -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.
*/
//==============================================================================