diff --git a/Builds/VisualStudio2015/RippleD.vcxproj b/Builds/VisualStudio2015/RippleD.vcxproj index 437cda3e4c..526470a6d3 100644 --- a/Builds/VisualStudio2015/RippleD.vcxproj +++ b/Builds/VisualStudio2015/RippleD.vcxproj @@ -1782,6 +1782,8 @@ + + @@ -1790,6 +1792,10 @@ True True + + True + True + True True @@ -4546,6 +4552,9 @@ True True + + True + True diff --git a/Builds/VisualStudio2015/RippleD.vcxproj.filters b/Builds/VisualStudio2015/RippleD.vcxproj.filters index c2c4a4a0ca..52ba9c06e8 100644 --- a/Builds/VisualStudio2015/RippleD.vcxproj.filters +++ b/Builds/VisualStudio2015/RippleD.vcxproj.filters @@ -2421,6 +2421,9 @@ ripple\conditions + + ripple\conditions + ripple\conditions @@ -2430,6 +2433,9 @@ ripple\conditions\impl + + ripple\conditions\impl + ripple\conditions\impl @@ -5328,6 +5334,9 @@ test\beast + + test\conditions + test\conditions diff --git a/src/ripple/conditions/Condition.h b/src/ripple/conditions/Condition.h index 1d48329765..e36ed25c7c 100644 --- a/src/ripple/conditions/Condition.h +++ b/src/ripple/conditions/Condition.h @@ -127,6 +127,9 @@ validate (Condition const& c) if (c.type == condition_hashlock) return (c.featureBitmask == (feature_sha256 | feature_preimage)); + if (c.type == condition_ed25519) + return (c.featureBitmask == feature_ed25519); + return false; } diff --git a/src/ripple/conditions/Ed25519.h b/src/ripple/conditions/Ed25519.h new file mode 100644 index 0000000000..d126b425dd --- /dev/null +++ b/src/ripple/conditions/Ed25519.h @@ -0,0 +1,100 @@ +//------------------------------------------------------------------------------ +/* + 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_ED25519_H +#define RIPPLE_CONDITIONS_ED25519_H + +#include +#include +#include +#include +#include +#include + +namespace ripple { +namespace cryptoconditions { + +class Ed25519 final + : public Fulfillment +{ + static std::size_t constexpr signature_size_ = 64; + static std::size_t constexpr pubkey_size_ = 32; + + std::array payload_; + +public: + Ed25519 () = default; + + /** Create a fulfillment given a keypair and the message */ + Ed25519 ( + SecretKey const& secretKey, + PublicKey const& publicKey, + Slice message); + + /** Create a fulfillment given a secret key and the message */ + Ed25519 ( + SecretKey const& secretKey, + Slice message); + + Condition + condition() const override; + + std::uint16_t + type () const override + { + return condition_ed25519; + } + + std::uint32_t + features () const override + { + return feature_ed25519; + } + + bool + ok () const override + { + return true; + } + + std::size_t + payloadSize () const override + { + return payload_.size(); + } + + Buffer + payload() const override + { + return { payload_.data(), payload_.size() }; + } + + bool + validate (Slice data) const override; + + bool + parsePayload (Slice s) override; +}; + +} + +} + +#endif diff --git a/src/ripple/conditions/impl/Ed25519.cpp b/src/ripple/conditions/impl/Ed25519.cpp new file mode 100644 index 0000000000..59d6dc9fbc --- /dev/null +++ b/src/ripple/conditions/impl/Ed25519.cpp @@ -0,0 +1,115 @@ +//------------------------------------------------------------------------------ +/* + 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 + +namespace ripple { +namespace cryptoconditions { + +Ed25519::Ed25519 ( + SecretKey const& secretKey, + PublicKey const& publicKey, + Slice message) +{ + if (publicKeyType (publicKey) != KeyType::ed25519) + LogicError ("An Ed25519 public key is required."); + + // When PublicKey wraps an Ed25519 key it prefixes + // the key itself with a 0xED byte. We carefully + // skip that byte. + std::memcpy ( + payload_.data(), + publicKey.data() + 1, + publicKey.size() - 1); + + // Now sign: + ed25519_sign ( + message.data(), + message.size(), + secretKey.data(), + payload_.data(), + payload_.data() + pubkey_size_); +} + +/** Create a fulfillment given a secret key and the message */ +Ed25519::Ed25519 ( + SecretKey const& secretKey, + Slice message) +{ + // First derive the public key, and place it in the + // payload: + ed25519_publickey ( + secretKey.data(), + payload_.data()); + + ed25519_sign ( + message.data(), + message.size(), + secretKey.data(), + payload_.data(), + payload_.data() + pubkey_size_); +} + +Condition +Ed25519::condition() const +{ + Condition cc; + cc.type = type(); + cc.featureBitmask = features(); + cc.maxFulfillmentLength = payloadSize(); + + std::memcpy ( + cc.fingerprint.data(), + payload_.data(), + pubkey_size_); + + return cc; +} + +bool +Ed25519::validate (Slice data) const +{ + return ed25519_sign_open ( + data.data(), + data.size(), + payload_.data(), + payload_.data() + pubkey_size_) == 0; +} + +bool +Ed25519::parsePayload (Slice s) +{ + // The payload consists of 96 consecutive bytes: + // The public key is the first 32 and the + // remaining 64 bytes are the signature. + if (s.size() != sizeof(payload_)) + return false; + + std::memcpy (payload_.data(), s.data(), s.size()); + return true; +} + +} + +} diff --git a/src/ripple/conditions/impl/Fulfillment.cpp b/src/ripple/conditions/impl/Fulfillment.cpp index 3a866021a9..b04a7b0716 100644 --- a/src/ripple/conditions/impl/Fulfillment.cpp +++ b/src/ripple/conditions/impl/Fulfillment.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -79,6 +80,10 @@ loadFulfillment (std::uint16_t type, Slice payload) p = std::make_unique(); break; + case condition_ed25519: + p = std::make_unique(); + break; + default: throw std::domain_error ( "Unknown cryptocondition type " + diff --git a/src/ripple/protocol/SecretKey.h b/src/ripple/protocol/SecretKey.h index 384ddedbda..91e4210442 100644 --- a/src/ripple/protocol/SecretKey.h +++ b/src/ripple/protocol/SecretKey.h @@ -43,6 +43,7 @@ public: ~SecretKey(); + SecretKey (std::array const& data); SecretKey (Slice const& slice); std::uint8_t const* diff --git a/src/ripple/protocol/impl/SecretKey.cpp b/src/ripple/protocol/impl/SecretKey.cpp index c481e21f81..4ea7917c73 100644 --- a/src/ripple/protocol/impl/SecretKey.cpp +++ b/src/ripple/protocol/impl/SecretKey.cpp @@ -36,6 +36,11 @@ SecretKey::~SecretKey() beast::secure_erase(buf_, sizeof(buf_)); } +SecretKey::SecretKey (std::array const& key) +{ + std::memcpy(buf_, key.data(), key.size()); +} + SecretKey::SecretKey (Slice const& slice) { if (slice.size() != sizeof(buf_)) diff --git a/src/ripple/unity/conditions.cpp b/src/ripple/unity/conditions.cpp index 41bb21824a..a6cb3af12e 100644 --- a/src/ripple/unity/conditions.cpp +++ b/src/ripple/unity/conditions.cpp @@ -21,3 +21,4 @@ #include #include +#include diff --git a/src/test/conditions/Ed25519_test.cpp b/src/test/conditions/Ed25519_test.cpp new file mode 100644 index 0000000000..b314b89ad9 --- /dev/null +++ b/src/test/conditions/Ed25519_test.cpp @@ -0,0 +1,199 @@ +//------------------------------------------------------------------------------ +/* + 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 { + +class Ed25519_test : public beast::unit_test::suite +{ + void + check ( + std::array const& secretKey, + std::vector const& message, + std::string const& fulfillment, + std::string const& condition) + { + SecretKey const sk { makeSlice (secretKey) }; + PublicKey const pk = derivePublicKey (KeyType::ed25519, sk); + + auto f = loadFulfillment (fulfillment); + auto c = loadCondition (condition); + + BEAST_EXPECT (f); + BEAST_EXPECT (c); + + if (f && c) + { + // Ensure that loading works correctly + BEAST_EXPECT (to_string (*f) == fulfillment); + BEAST_EXPECT (to_string (*c) == condition); + + // Ensures that the fulfillment generates + // the condition correctly: + BEAST_EXPECT (f->condition() == c); + + // Check fulfillment + BEAST_EXPECT (validate (*f, *c, makeSlice(message))); + + // Check correct creation of fulfillment + BEAST_EXPECT (*f == Ed25519 (sk, pk, makeSlice(message))); + } + } + + void testKnownVectors () + { + testcase ("Known Vectors"); + + std::array sk = + {{ + 0x50, 0xd8, 0x58, 0xe0, 0x98, 0x5e, 0xcc, 0x7f, + 0x60, 0x41, 0x8a, 0xaf, 0x0c, 0xc5, 0xab, 0x58, + 0x7f, 0x42, 0xc2, 0x57, 0x0a, 0x88, 0x40, 0x95, + 0xa9, 0xe8, 0xcc, 0xac, 0xd0, 0xf6, 0x54, 0x5c + }}; + + std::vector const payload (512, 0x21); + + check (sk, payload, + "cf:4:RCmTBlAEqh5MSPTdAVgZTAI0m8xmTNluQA6iaZGKjVGfTbzglso5Uo3i2O2WVP6abH1dz5k0H5DLylizTeL5UC0VSptUN4VCkhtbwx3B00pCeWNy1H78rq6OTXzok-EH", + "cc:4:20:RCmTBlAEqh5MSPTdAVgZTAI0m8xmTNluQA6iaZGKjVE:96"); + + sk.fill (0x00); + check (sk, hexblob (""), + "cf:4:O2onvM62pC1io6jQKm8Nc2UyFXcd4kOmOsBIoYtZ2imPiVs8r-LJUGA50OKmY4JWgARnT-jSN3hQkuQNaq9IPk_GAWhwXzHxAVlhOM4hqjV8DTKgZPQj3D7kqjq_U_gD", + "cc:4:20:O2onvM62pC1io6jQKm8Nc2UyFXcd4kOmOsBIoYtZ2ik:96"); + + sk.fill (0xff); + check (sk, hexblob ("616263"), + "cf:4:dqFZIESm5PURJlvKc6YE2QsFKdHfYCvjChmpJXZg0fWuxqtqkSKv8PfcuWZ_9hMTaJRzK254wm9bZzEB4mf-Litl-k1T2tR4oa2mTVD9Hf232Ukg3D4aVkpkexy6NWAB", + "cc:4:20:dqFZIESm5PURJlvKc6YE2QsFKdHfYCvjChmpJXZg0fU:96"); + } + + void testFulfillment () + { + testcase ("Fulfillment"); + + std::array sk = + {{ + 0x50, 0xd8, 0x58, 0xe0, 0x98, 0x5e, 0xcc, 0x7f, + 0x60, 0x41, 0x8a, 0xaf, 0x0c, 0xc5, 0xab, 0x58, + 0x7f, 0x42, 0xc2, 0x57, 0x0a, 0x88, 0x40, 0x95, + 0xa9, 0xe8, 0xcc, 0xac, 0xd0, 0xf6, 0x54, 0x5c + }}; + + std::vector const v1 (512, 0x21); + std::vector const v2 (512, 0x22); + + Ed25519 const f ({ sk }, makeSlice(v1)); + + // First check against incorrect conditions: + char const* const ccs[] = + { + "cc:0:3:PWh2oBRt6FdusjlahY3hIT0bksZbd53zozHP1aRYRUY:256", + "cc:1:25:XkflBmyISKuevH8-850LuMrzN-HT1Ds9zKUEzaZ2Wk0:103", + "cc:2:2b:d3O4epRCo_3rj17Bf3v8hp5ig7vq84ivPok07T9Rdl0:146", + "cc:3:11:uKkFs6dhGZCwD51c69vVvHYSp25cRi9IlvXfFaxhMjo:518", + "cc:4:20:O2onvM62pC1io6jQKm8Nc2UyFXcd4kOmOsBIoYtZ2ik:96" + }; + + for (auto cc : ccs) + { + auto c = loadCondition (cc); + + if (BEAST_EXPECT (c)) + { + BEAST_EXPECT (! validate (f, c.get(), makeSlice(v1))); + BEAST_EXPECT (! validate (f, c.get(), makeSlice(v2))); + } + } + + // Now, finally, check the correct condition: + auto c = loadCondition ( + "cc:4:20:RCmTBlAEqh5MSPTdAVgZTAI0m8xmTNluQA6iaZGKjVE:96"); + + if (BEAST_EXPECT (c)) + { + BEAST_EXPECT (validate (f, c.get(), makeSlice(v1))); + BEAST_EXPECT (! validate (f, c.get(), makeSlice(v2))); + } + + // Under the existing spec, multiple messages sharing + // the same key should generate the same fulfillment: + { + Ed25519 const f1 ({ sk }, makeSlice (v1)); + Ed25519 const f2 ({ sk }, makeSlice (v2)); + + BEAST_EXPECT (f1.condition () == f2.condition ()); + } + } + + void testMalformedCondition () + { + testcase ("Malformed Condition"); + + // This is malformed and will not load because a + // feature suite of 0 is not supported. + auto c1 = loadCondition ( + "cc:4:0:RCmTBlAEqh5MSPTdAVgZTAI0m8xmTNluQA6iaZGKjVE:96"); + BEAST_EXPECT (!c1); + + // The following will load but fail in different ways + auto c2 = loadCondition ( // only sha256 + "cc:4:1:RCmTBlAEqh5MSPTdAVgZTAI0m8xmTNluQA6iaZGKjVE:96"); + BEAST_EXPECT (c2 && !validate(*c2)); + + auto c3 = loadCondition ( // only preimage + "cc:4:2:RCmTBlAEqh5MSPTdAVgZTAI0m8xmTNluQA6iaZGKjVE:96"); + BEAST_EXPECT (c3 && !validate(*c3)); + + auto c4 = loadCondition ( // sha256+preimage + "cc:4:3:RCmTBlAEqh5MSPTdAVgZTAI0m8xmTNluQA6iaZGKjVE:96"); + BEAST_EXPECT (c4 && !validate(*c4)); + + auto c5 = loadCondition ( // Ed25519+sha256+preimage + "cc:1:23:Yja3qFj7NS_VwwE7aJjPJos-uFCzStJlJLD4VsNy2XM:1"); + BEAST_EXPECT (c5 && !validate(*c5)); + + auto c6 = loadCondition ( // Ed25519+threshold + "cc:1:28:Yja3qFj7NS_VwwE7aJjPJos-uFCzStJlJLD4VsNy2XM:1"); + BEAST_EXPECT (c6 && !validate(*c6)); + } + + void run () + { + testKnownVectors (); + testFulfillment (); + testMalformedCondition (); + } +}; + +BEAST_DEFINE_TESTSUITE (Ed25519, conditions, ripple); + +} + +} diff --git a/src/unity/conditions_test_unity.cpp b/src/unity/conditions_test_unity.cpp index 21032b06e5..2387ee7e63 100644 --- a/src/unity/conditions_test_unity.cpp +++ b/src/unity/conditions_test_unity.cpp @@ -18,3 +18,4 @@ //============================================================================== #include +#include