diff --git a/Builds/VisualStudio2013/RippleD.vcxproj b/Builds/VisualStudio2013/RippleD.vcxproj index 3e6921ddb..1e3644e3d 100644 --- a/Builds/VisualStudio2013/RippleD.vcxproj +++ b/Builds/VisualStudio2013/RippleD.vcxproj @@ -2213,6 +2213,10 @@ True True + + True + True + True True @@ -2227,6 +2231,8 @@ True True + + diff --git a/Builds/VisualStudio2013/RippleD.vcxproj.filters b/Builds/VisualStudio2013/RippleD.vcxproj.filters index 7fb871e71..84ddda7dd 100644 --- a/Builds/VisualStudio2013/RippleD.vcxproj.filters +++ b/Builds/VisualStudio2013/RippleD.vcxproj.filters @@ -2970,6 +2970,9 @@ ripple\crypto\impl + + ripple\crypto\impl + ripple\crypto\impl @@ -2982,6 +2985,9 @@ ripple\crypto\impl + + ripple\crypto + ripple\crypto diff --git a/src/ripple/app/ledger/tests/Ledger_test.cpp b/src/ripple/app/ledger/tests/Ledger_test.cpp index 70597aa52..bab7833e1 100644 --- a/src/ripple/app/ledger/tests/Ledger_test.cpp +++ b/src/ripple/app/ledger/tests/Ledger_test.cpp @@ -24,24 +24,24 @@ namespace test { class Ledger_test : public beast::unit_test::suite { - void test_genesisLedger (bool sign) + void test_genesisLedger (bool sign, KeyType keyType) { std::uint64_t const xrp = std::mega::num; - auto master = createAccount ("masterpassphrase"); + auto master = createAccount ("masterpassphrase", keyType); Ledger::pointer LCL = createGenesisLedger(100000*xrp, master); Ledger::pointer ledger = std::make_shared(false, *LCL); // User accounts - auto gw1 = createAccount ("gw1"); + auto gw1 = createAccount ("gw1", keyType); expect (gw1.pk != master.pk, "gw1.pk != master.pk"); expect (gw1.sk != master.sk, "gw1.sk != master.sk"); - auto gw2 = createAccount ("gw2"); - auto gw3 = createAccount ("gw3"); - auto alice = createAccount ("alice"); - auto mark = createAccount ("mark"); + auto gw2 = createAccount ("gw2", keyType); + auto gw3 = createAccount ("gw3", keyType); + auto alice = createAccount ("alice", keyType); + auto mark = createAccount ("mark", keyType); // Fund gw1, gw2, gw3, alice, mark from master makeAndApplyPayment(master, gw1, 5000 * xrp, ledger, sign); @@ -90,17 +90,17 @@ class Ledger_test : public beast::unit_test::suite pass (); } - void test_unsigned_fails () + void test_unsigned_fails (KeyType keyType) { std::uint64_t const xrp = std::mega::num; - auto master = createAccount ("masterpassphrase"); + auto master = createAccount ("masterpassphrase", keyType); Ledger::pointer LCL = createGenesisLedger (100000 * xrp, master); Ledger::pointer ledger = std::make_shared (false, *LCL); - auto gw1 = createAccount ("gw1"); + auto gw1 = createAccount ("gw1", keyType); auto tx = getPaymentTx(master, gw1, 5000 * xrp, false); @@ -129,16 +129,15 @@ class Ledger_test : public beast::unit_test::suite public: void run () { - testcase ("genesisLedger signed transactions"); - test_genesisLedger (true); + test_genesisLedger (true, KeyType::secp256k1); + test_genesisLedger (true, KeyType::ed25519); - testcase ("genesisLedger unsigned transactions"); - test_genesisLedger (false); + test_genesisLedger (false, KeyType::secp256k1); + test_genesisLedger (false, KeyType::ed25519); - testcase ("unsigned invalid"); - test_unsigned_fails (); + test_unsigned_fails (KeyType::secp256k1); + test_unsigned_fails (KeyType::ed25519); - testcase ("getQuality"); test_getQuality (); } }; diff --git a/src/ripple/app/tests/common_ledger.cpp b/src/ripple/app/tests/common_ledger.cpp index 870370f34..58559436b 100644 --- a/src/ripple/app/tests/common_ledger.cpp +++ b/src/ripple/app/tests/common_ledger.cpp @@ -18,6 +18,7 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. //============================================================================== #include +#include namespace ripple { namespace test { @@ -90,16 +91,16 @@ createGenesisLedger(std::uint64_t start_amount_drops, TestAccount const& master) // Create an account represented by public RippleAddress and private // RippleAddress TestAccount -createAccount(std::string const& passphrase) +createAccount(std::string const& passphrase, KeyType keyType) { RippleAddress const seed = RippleAddress::createSeedGeneric(passphrase); - RippleAddress const generator - = RippleAddress::createGeneratorPublic(seed); + + auto keyPair = generateKeysFromSeed(keyType, seed); return { - std::move(RippleAddress::createAccountPublic(generator, 0)), - std::move(RippleAddress::createAccountPrivate(generator, seed, 0)), + std::move(keyPair.publicKey), + std::move(keyPair.secretKey), std::uint64_t(0) }; } diff --git a/src/ripple/app/tests/common_ledger.h b/src/ripple/app/tests/common_ledger.h index 403113069..4b9933b75 100644 --- a/src/ripple/app/tests/common_ledger.h +++ b/src/ripple/app/tests/common_ledger.h @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -75,7 +76,7 @@ createGenesisLedger(std::uint64_t start_amount_drops, TestAccount const& master) // Create an account represented by public RippleAddress and private // RippleAddress TestAccount -createAccount(std::string const& passphrase); +createAccount(std::string const& passphrase, KeyType keyType); void freezeAccount(TestAccount& account, Ledger::pointer const& ledger, bool sign = true); diff --git a/src/ripple/crypto/KeyType.h b/src/ripple/crypto/KeyType.h new file mode 100644 index 000000000..e555e8e5f --- /dev/null +++ b/src/ripple/crypto/KeyType.h @@ -0,0 +1,42 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2015 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_CRYPTO_KEYTYPE_H_INCLUDED +#define RIPPLE_CRYPTO_KEYTYPE_H_INCLUDED + +#include + +namespace ripple { + +enum class KeyType +{ + invalid = -1, + unknown = invalid, + + secp256k1 = 0, + ed25519 = 1, +}; + +KeyType keyTypeFromString (std::string const& s); + +const char* to_string (KeyType type); + +} + +#endif diff --git a/src/ripple/crypto/impl/KeyType.cpp b/src/ripple/crypto/impl/KeyType.cpp new file mode 100644 index 000000000..86e90becd --- /dev/null +++ b/src/ripple/crypto/impl/KeyType.cpp @@ -0,0 +1,40 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2015 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 + +namespace ripple { + +KeyType keyTypeFromString (std::string const& s) +{ + if (s == "secp256k1") return KeyType::secp256k1; + if (s == "ed25519" ) return KeyType::ed25519; + + return KeyType::invalid; +} + +const char* to_string (KeyType type) +{ + return type == KeyType::secp256k1 ? "secp256k1" + : type == KeyType::ed25519 ? "ed25519" + : "INVALID"; +} + +} diff --git a/src/ripple/protocol/AnyPublicKey.h b/src/ripple/protocol/AnyPublicKey.h index 059e5ebc6..f909c681e 100644 --- a/src/ripple/protocol/AnyPublicKey.h +++ b/src/ripple/protocol/AnyPublicKey.h @@ -21,6 +21,7 @@ #define RIPPLE_PROTOCOL_ANYPUBLICKEY_H_INCLUDED #include +#include #include #include #include @@ -34,15 +35,6 @@ namespace ripple { -enum class KeyType -{ - unknown, - secp256k1, - ed25519 -}; - -//------------------------------------------------------------------------------ - /** Variant container for all public keys. */ class AnyPublicKeySlice : public Slice diff --git a/src/ripple/protocol/JsonFields.h b/src/ripple/protocol/JsonFields.h index fed1d0fa7..db13702f8 100644 --- a/src/ripple/protocol/JsonFields.h +++ b/src/ripple/protocol/JsonFields.h @@ -177,6 +177,7 @@ JSS ( issuer ); // in: RipplePathFind, Subscribe, // Unsubscribe, BookOffers // out: paths/Node, STPathSet, STAmount JSS ( key ); // out: WalletSeed +JSS ( key_type ); // in/out: WalletPropose, TransactionSign JSS ( last ); // out: RPCVersion JSS ( last_close ); // out: NetworkOPs JSS ( ledger ); // in: NetworkOPs, LedgerCleaner, @@ -296,6 +297,7 @@ JSS ( search_depth ); // in: RipplePathFind JSS ( secret ); // in: TransactionSign, WalletSeed, // ValidationCreate, ValidationSeed JSS ( seed ); // in: WalletAccounts, out: WalletSeed +JSS ( seed_hex ); // in: WalletPropose, TransactionSign JSS ( send_currencies ); // out: AccountCurrencies JSS ( seq ); // in: LedgerEntry; // out: NetworkOPs, RPCSub, AccountOffers diff --git a/src/ripple/protocol/RippleAddress.h b/src/ripple/protocol/RippleAddress.h index dfb66cbaf..b84e5be22 100644 --- a/src/ripple/protocol/RippleAddress.h +++ b/src/ripple/protocol/RippleAddress.h @@ -20,8 +20,11 @@ #ifndef RIPPLE_PROTOCOL_RIPPLEADDRESS_H_INCLUDED #define RIPPLE_PROTOCOL_RIPPLEADDRESS_H_INCLUDED +#include #include #include +#include +#include #include #include #include @@ -142,7 +145,7 @@ public: void setAccountPublic (Blob const& vPublic); void setAccountPublic (RippleAddress const& generator, int seq); - bool accountPublicVerify (uint256 const& uHash, Blob const& vucSig, + bool accountPublicVerify (Blob const& message, Blob const& vucSig, ECDSA mustBeFullyCanonical) const; static RippleAddress createAccountPublic (Blob const& vPublic) @@ -172,7 +175,7 @@ public: void setAccountPrivate (RippleAddress const& naGenerator, RippleAddress const& naSeed, int seq); - bool accountPrivateSign (uint256 const& uHash, Blob& vucSig) const; + Blob accountPrivateSign (Blob const& message) const; // Encrypt a message. Blob accountPrivateEncrypt ( @@ -285,6 +288,18 @@ operator>=(RippleAddress const& lhs, RippleAddress const& rhs) return !(lhs < rhs); } +struct KeyPair +{ + RippleAddress secretKey; + RippleAddress publicKey; +}; + +uint256 keyFromSeed (uint128 const& seed); + +RippleAddress getSeedFromRPC (Json::Value const& params); + +KeyPair generateKeysFromSeed (KeyType keyType, RippleAddress const& seed); + } // ripple #endif diff --git a/src/ripple/protocol/impl/RippleAddress.cpp b/src/ripple/protocol/impl/RippleAddress.cpp index 7109a59f9..89bf97516 100644 --- a/src/ripple/protocol/impl/RippleAddress.cpp +++ b/src/ripple/protocol/impl/RippleAddress.cpp @@ -25,16 +25,40 @@ #include #include #include +#include #include #include #include #include +#include #include #include +#include #include namespace ripple { +static +bool isCanonicalEd25519Signature (std::uint8_t const* signature) +{ + using std::uint8_t; + + // Big-endian `l`, the Ed25519 subgroup order + char const* const order = "\x10\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00" + "\x14\xDE\xF9\xDE\xA2\xF7\x9C\xD6" + "\x58\x12\x63\x1A\x5C\xF5\xD3\xED"; + + uint8_t const* const l = reinterpret_cast (order); + + // Take the second half of signature and byte-reverse it to big-endian. + uint8_t const* S_le = signature + 32; + uint8_t S[32]; + std::reverse_copy (S_le, S_le + 32, S); + + return std::lexicographical_compare (S, S + 32, l, l + 32); +} + // <-- seed static uint128 PassPhraseToKey (std::string const& passPhrase) { @@ -486,8 +510,28 @@ void RippleAddress::setAccountPublic (RippleAddress const& generator, int seq) } bool RippleAddress::accountPublicVerify ( - uint256 const& uHash, Blob const& vucSig, ECDSA fullyCanonical) const + Blob const& message, Blob const& vucSig, ECDSA fullyCanonical) const { + if (vchData.size() == 33 && vchData[0] == 0xED) + { + if (vucSig.size() != 64) + { + return false; + } + + uint8_t const* publicKey = &vchData[1]; + uint8_t const* signature = &vucSig[0]; + + if (ed25519_sign_open (message.data(), message.size(), publicKey, signature) != 0) + { + return false; + } + + return isCanonicalEd25519Signature (signature); + } + + uint256 const uHash = getSHA512Half (message); + return verifySignature (getAccountPublic(), uHash, vucSig, fullyCanonical); } @@ -521,7 +565,7 @@ uint256 RippleAddress::getAccountPrivate () const throw std::runtime_error ("unset source - getAccountPrivate"); case VER_ACCOUNT_PRIVATE: - return uint256 (vchData); + return uint256::fromVoid (vchData.data() + (vchData.size() - 32)); default: throw std::runtime_error ("bad source: " + std::to_string(nVersion)); @@ -556,15 +600,32 @@ void RippleAddress::setAccountPrivate ( setAccountPrivate (secretKey); } -bool RippleAddress::accountPrivateSign (uint256 const& uHash, Blob& vucSig) const +Blob RippleAddress::accountPrivateSign (Blob const& message) const { - vucSig = ECDSASign (uHash, getAccountPrivate()); - const bool ok = !vucSig.empty(); + if (vchData.size() == 33 && vchData[0] == 0xED) + { + uint8_t const* secretKey = &vchData[1]; + ed25519_public_key publicKey; + Blob signature (sizeof (ed25519_signature)); + + ed25519_publickey (secretKey, publicKey); + + ed25519_sign (message.data(), message.size(), secretKey, publicKey, &signature[0]); + + assert (isCanonicalEd25519Signature (signature.data())); + + return signature; + } + + uint256 const uHash = getSHA512Half (message); + + Blob result = ECDSASign (uHash, getAccountPrivate()); + bool const ok = !result.empty(); CondLog (!ok, lsWARNING, RippleAddress) << "accountPrivateSign: Signing failed."; - return ok; + return result; } Blob RippleAddress::accountPrivateEncrypt ( @@ -813,4 +874,104 @@ RippleAddress RippleAddress::createSeedGeneric (std::string const& strText) return naNew; } +uint256 keyFromSeed (uint128 const& seed) +{ + Serializer s; + + s.add128 (seed); + uint256 result = s.getSHA512Half(); + + s.secureErase (); + + return result; +} + +RippleAddress getSeedFromRPC (Json::Value const& params) +{ + // This function is only called when `key_type` is present. + assert (params.isMember (jss::key_type)); + + bool const has_passphrase = params.isMember (jss::passphrase); + bool const has_seed = params.isMember (jss::seed); + bool const has_seed_hex = params.isMember (jss::seed_hex); + + int const n_secrets = has_passphrase + has_seed + has_seed_hex; + + if (n_secrets > 1) + { + // `passphrase`, `seed`, and `seed_hex` are mutually exclusive. + return RippleAddress(); + } + + RippleAddress result; + + if (has_seed) + { + std::string const seed = params[jss::seed].asString(); + + result.setSeed (seed); + } + else if (has_seed_hex) + { + uint128 seed; + std::string const seed_hex = params[jss::seed_hex].asString(); + + if (seed_hex.size() != 32 || !seed.SetHex (seed_hex, true)) + { + return RippleAddress(); + } + + result.setSeed (seed); + } + else if (has_passphrase) + { + std::string const passphrase = params[jss::passphrase].asString(); + + // Given `key_type`, `passphrase` is always the passphrase. + uint128 const seed = PassPhraseToKey (passphrase); + result.setSeed (seed); + } + + return result; +} + +KeyPair generateKeysFromSeed (KeyType type, RippleAddress const& seed) +{ + KeyPair result; + + if (! seed.isSet()) + { + return result; + } + + if (type == KeyType::secp256k1) + { + RippleAddress generator = RippleAddress::createGeneratorPublic (seed); + result.secretKey.setAccountPrivate (generator, seed, 0); + result.publicKey.setAccountPublic (generator, 0); + } + else if (type == KeyType::ed25519) + { + uint256 secretkey = keyFromSeed (seed.getSeed()); + + Blob ed25519_key (33); + ed25519_key[0] = 0xED; + + assert (secretkey.size() + 1 == ed25519_key.size()); + memcpy (&ed25519_key[1], secretkey.data(), secretkey.size()); + result.secretKey.setAccountPrivate (ed25519_key); + + ed25519_publickey (secretkey.data(), &ed25519_key[1]); + result.publicKey.setAccountPublic (ed25519_key); + + secretkey.zero(); // security erase + } + else + { + assert (false); // not reached + } + + return result; +} + } // ripple diff --git a/src/ripple/protocol/impl/STTx.cpp b/src/ripple/protocol/impl/STTx.cpp index b6ea42c36..0f009d342 100644 --- a/src/ripple/protocol/impl/STTx.cpp +++ b/src/ripple/protocol/impl/STTx.cpp @@ -152,6 +152,14 @@ STTx::getMentionedAccounts () const return accounts; } +static Blob getSigningData (STTx const& that) +{ + Serializer s; + s.add32 (HashPrefix::txSign); + that.add (s, false); + return s.getData(); +} + uint256 STTx::getSigningHash () const { @@ -178,8 +186,7 @@ Blob STTx::getSignature () const void STTx::sign (RippleAddress const& private_key) { - Blob signature; - private_key.accountPrivateSign (getSigningHash (), signature); + Blob const signature = private_key.accountPrivateSign (getSigningData (*this)); setFieldVL (sfTxnSignature, signature); } @@ -196,7 +203,7 @@ bool STTx::checkSign () const RippleAddress n; n.setAccountPublic (getFieldVL (sfSigningPubKey)); - sig_state_ = n.accountPublicVerify (getSigningHash (), + sig_state_ = n.accountPublicVerify (getSigningData (*this), getFieldVL (sfTxnSignature), fullyCanonical); } catch (...) diff --git a/src/ripple/protocol/tests/RippleAddress.test.cpp b/src/ripple/protocol/tests/RippleAddress.test.cpp index fa06fa859..c6bdfcba1 100644 --- a/src/ripple/protocol/tests/RippleAddress.test.cpp +++ b/src/ripple/protocol/tests/RippleAddress.test.cpp @@ -18,15 +18,15 @@ //============================================================================== #include +#include #include #include #include #include -#include namespace ripple { -class RippleAddress_test : public beast::unit_test::suite +class RippleAddress_test : public ripple::TestSuite { public: void run() @@ -57,6 +57,16 @@ public: expect (generator.humanGenerator () == "fhuJKrhSDzV2SkjLn9qbwm5AaRmrxDPfFsHDCP6yfDZWcxDFz4mt", generator.humanGenerator ()); + // Create ed25519 account public/private key pair. + KeyPair keys = generateKeysFromSeed (KeyType::ed25519, naSeed); + expectEquals (keys.publicKey.humanAccountPublic(), "aKGheSBjmCsKJVuLNKRAKpZXT6wpk2FCuEZAXJupXgdAxX5THCqR"); + + // Check ed25519 account signing. + vucTextSig = keys.secretKey.accountPrivateSign (vucTextSrc); + + expect (!vucTextSig.empty(), "ed25519 signing failed."); + expect (keys.publicKey.accountPublicVerify (vucTextSrc, vucTextSig, ECDSA()), "ed25519 verify failed."); + // Create account #0 public/private key pair. RippleAddress naAccountPublic0 = RippleAddress::createAccountPublic (generator, 0); RippleAddress naAccountPrivate0 = RippleAddress::createAccountPrivate (generator, naSeed, 0); @@ -72,15 +82,19 @@ public: expect (naAccountPublic1.humanAccountPublic () == "aBPXpTfuLy1Bhk3HnGTTAqnovpKWQ23NpFMNkAF6F1Atg5vDyPrw", naAccountPublic1.humanAccountPublic ()); // Check account signing. - expect (naAccountPrivate0.accountPrivateSign (uHash, vucTextSig), "Signing failed."); - expect (naAccountPublic0.accountPublicVerify (uHash, vucTextSig, ECDSA::strict), "Verify failed."); - expect (!naAccountPublic1.accountPublicVerify (uHash, vucTextSig, ECDSA::not_strict), "Anti-verify failed."); - expect (!naAccountPublic1.accountPublicVerify (uHash, vucTextSig, ECDSA::strict), "Anti-verify failed."); + vucTextSig = naAccountPrivate0.accountPrivateSign (vucTextSrc); - expect (naAccountPrivate1.accountPrivateSign (uHash, vucTextSig), "Signing failed."); - expect (naAccountPublic1.accountPublicVerify (uHash, vucTextSig, ECDSA::strict), "Verify failed."); - expect (!naAccountPublic0.accountPublicVerify (uHash, vucTextSig, ECDSA::not_strict), "Anti-verify failed."); - expect (!naAccountPublic0.accountPublicVerify (uHash, vucTextSig, ECDSA::strict), "Anti-verify failed."); + expect (!vucTextSig.empty(), "Signing failed."); + expect (naAccountPublic0.accountPublicVerify (vucTextSrc, vucTextSig, ECDSA::strict), "Verify failed."); + expect (!naAccountPublic1.accountPublicVerify (vucTextSrc, vucTextSig, ECDSA::not_strict), "Anti-verify failed."); + expect (!naAccountPublic1.accountPublicVerify (vucTextSrc, vucTextSig, ECDSA::strict), "Anti-verify failed."); + + vucTextSig = naAccountPrivate1.accountPrivateSign (vucTextSrc); + + expect (!vucTextSig.empty(), "Signing failed."); + expect (naAccountPublic1.accountPublicVerify (vucTextSrc, vucTextSig, ECDSA::strict), "Verify failed."); + expect (!naAccountPublic0.accountPublicVerify (vucTextSrc, vucTextSig, ECDSA::not_strict), "Anti-verify failed."); + expect (!naAccountPublic0.accountPublicVerify (vucTextSrc, vucTextSig, ECDSA::strict), "Anti-verify failed."); // Check account encryption. Blob vucTextCipher diff --git a/src/ripple/rpc/handlers/WalletPropose.cpp b/src/ripple/rpc/handlers/WalletPropose.cpp index a01fbe7b3..a14fc9895 100644 --- a/src/ripple/rpc/handlers/WalletPropose.cpp +++ b/src/ripple/rpc/handlers/WalletPropose.cpp @@ -18,7 +18,10 @@ //============================================================================== #include +#include +#include #include +#include namespace ripple { @@ -35,14 +38,58 @@ Json::Value walletPropose (Json::Value const& params) RippleAddress naSeed; RippleAddress naAccount; - if (!params.isMember (jss::passphrase)) - naSeed.setSeedRandom (); + KeyType type = KeyType::secp256k1; - else if (!naSeed.setSeedGeneric (params[jss::passphrase].asString ())) + bool const has_key_type = params.isMember (jss::key_type); + bool const has_passphrase = params.isMember (jss::passphrase); + + if (has_key_type) + { + // `key_type` must be valid if present. + + type = keyTypeFromString (params[jss::key_type].asString()); + + if (type == KeyType::invalid) + { + return rpcError (rpcBAD_SEED); + } + + naSeed = getSeedFromRPC (params); + } + else if (has_passphrase) + { + naSeed.setSeedGeneric (params[jss::passphrase].asString()); + } + else + { + naSeed.setSeedRandom(); + } + + if (!naSeed.isSet()) + { return rpcError(rpcBAD_SEED); + } - RippleAddress naGenerator = RippleAddress::createGeneratorPublic (naSeed); - naAccount.setAccountPublic (naGenerator, 0); + if (type == KeyType::secp256k1) + { + RippleAddress naGenerator = RippleAddress::createGeneratorPublic (naSeed); + naAccount.setAccountPublic (naGenerator, 0); + } + else if (type == KeyType::ed25519) + { + uint256 secretkey = keyFromSeed (naSeed.getSeed()); + + Blob publickey (33); + publickey[0] = 0xED; + ed25519_publickey (secretkey.data(), &publickey[1]); + secretkey.zero(); // security erase + + naAccount.setAccountPublic (publickey); + } + else + { + assert (false); // not reached + } Json::Value obj (Json::objectValue); @@ -51,6 +98,7 @@ Json::Value walletPropose (Json::Value const& params) obj[jss::master_key] = naSeed.humanSeed1751(); obj[jss::account_id] = naAccount.humanAccountID (); obj[jss::public_key] = naAccount.humanAccountPublic(); + obj[jss::key_type] = to_string (type); auto acct = naAccount.getAccountPublic(); obj[jss::public_key_hex] = strHex(acct.begin(), acct.size()); diff --git a/src/ripple/rpc/impl/KeypairForSignature.cpp b/src/ripple/rpc/impl/KeypairForSignature.cpp index 16b9cd714..4b5e5b87f 100644 --- a/src/ripple/rpc/impl/KeypairForSignature.cpp +++ b/src/ripple/rpc/impl/KeypairForSignature.cpp @@ -18,6 +18,7 @@ //============================================================================== #include +#include #include namespace ripple { @@ -25,30 +26,60 @@ namespace RPC { KeyPair keypairForSignature (Json::Value const& params, Json::Value& error) { - if (! params.isMember ("secret")) - { - error = RPC::missing_field_error ("secret"); + bool const has_key_type = params.isMember (jss::key_type); + bool const has_passphrase = params.isMember (jss::passphrase); + bool const has_secret = params.isMember (jss::secret); + bool const has_seed = params.isMember (jss::seed); + bool const has_seed_hex = params.isMember (jss::seed_hex); + int const n_secrets = has_passphrase + has_secret + has_seed + has_seed_hex; + + if (n_secrets == 0) + { + error = RPC::missing_field_error (jss::secret); return KeyPair(); } + if (n_secrets > 1) + { + // `passphrase`, `secret`, `seed`, and `seed_hex` are mutually exclusive. + error = rpcError (rpcBAD_SECRET); + return KeyPair(); + } + + if (has_key_type && has_secret) + { + // `secret` is deprecated. + error = rpcError (rpcBAD_SECRET); + return KeyPair(); + } + + KeyType type = KeyType::secp256k1; + RippleAddress seed; - if (! seed.setSeedGeneric (params["secret"].asString ())) + if (has_key_type) + { + // `key_type` must be valid if present. + + type = keyTypeFromString (params[jss::key_type].asString()); + + if (type == KeyType::invalid) + { + error = rpcError (rpcBAD_SEED); + return KeyPair(); + } + + seed = getSeedFromRPC (params); + } + else + if (! seed.setSeedGeneric (params[jss::secret].asString ())) { error = RPC::make_error (rpcBAD_SEED, - RPC::invalid_field_message ("secret")); - - return KeyPair(); + RPC::invalid_field_message (jss::secret)); } - KeyPair result; - - RippleAddress generator = RippleAddress::createGeneratorPublic (seed); - result.secretKey.setAccountPrivate (generator, seed, 0); - result.publicKey.setAccountPublic (generator, 0); - - return result; + return generateKeysFromSeed (type, seed); } } // RPC diff --git a/src/ripple/rpc/impl/KeypairForSignature.h b/src/ripple/rpc/impl/KeypairForSignature.h index 66e84c82f..3eb32eea3 100644 --- a/src/ripple/rpc/impl/KeypairForSignature.h +++ b/src/ripple/rpc/impl/KeypairForSignature.h @@ -26,12 +26,6 @@ namespace ripple { namespace RPC { -struct KeyPair -{ - RippleAddress secretKey; - RippleAddress publicKey; -}; - KeyPair keypairForSignature (Json::Value const& params, Json::Value& error); } // RPC diff --git a/src/ripple/rpc/tests/KeyGeneration.test.cpp b/src/ripple/rpc/tests/KeyGeneration.test.cpp index 81e7d1589..7590e027d 100644 --- a/src/ripple/rpc/tests/KeyGeneration.test.cpp +++ b/src/ripple/rpc/tests/KeyGeneration.test.cpp @@ -56,6 +56,17 @@ static key_strings const secp256k1_strings = "1949ECD889EA71324BC7A30C8E81F4E93CB73EE19D59E9082111E78CC3DDABC2", }; +static key_strings const ed25519_strings = +{ + "r4qV6xTXerqaZav3MJfSY79ynmc1BSBev1", + common::master_key, + common::master_seed, + common::master_seed_hex, + "aKEQmgLMyZPMruJFejUuedp169LgW6DbJt1rej1DJ5hWUMH4pHJ7", + "ED54C3F5BEDA8BD588B203D23A27398FAD9D20F88A974007D6994659CD7273FE1D", + "77AAED2698D56D6676323629160F4EEF21CFD9EE3D0745CC78FA291461F98278", +}; + class WalletPropose_test : public ripple::TestSuite { public: @@ -65,19 +76,19 @@ public: Json::Value result = walletPropose (params); expect (! contains_error (result)); - expect (result.isMember ("account_id")); - expect (result.isMember ("master_key")); - expect (result.isMember ("master_seed")); - expect (result.isMember ("master_seed_hex")); - expect (result.isMember ("public_key")); - expect (result.isMember ("public_key_hex")); + expect (result.isMember (jss::account_id)); + expect (result.isMember (jss::master_key)); + expect (result.isMember (jss::master_seed)); + expect (result.isMember (jss::master_seed_hex)); + expect (result.isMember (jss::public_key)); + expect (result.isMember (jss::public_key_hex)); - std::string seed = result["master_seed"].asString(); + std::string seed = result[jss::master_seed].asString(); result = walletPropose (params); // We asked for two random seeds, so they shouldn't match. - expect (result["master_seed"].asString() != seed, seed); + expect (result[jss::master_seed].asString() != seed, seed); } void testSecretWallet (Json::Value const& params, key_strings const& s) @@ -85,12 +96,12 @@ public: Json::Value result = walletPropose (params); expect (! contains_error (result)); - expectEquals (result["account_id"], s.account_id); - expectEquals (result["master_key"], s.master_key); - expectEquals (result["master_seed"], s.master_seed); - expectEquals (result["master_seed_hex"], s.master_seed_hex); - expectEquals (result["public_key"], s.public_key); - expectEquals (result["public_key_hex"], s.public_key_hex); + expectEquals (result[jss::account_id], s.account_id); + expectEquals (result[jss::master_key], s.master_key); + expectEquals (result[jss::master_seed], s.master_seed); + expectEquals (result[jss::master_seed_hex], s.master_seed_hex); + expectEquals (result[jss::public_key], s.public_key); + expectEquals (result[jss::public_key_hex], s.public_key_hex); } void testLegacyPassphrase (char const* value) @@ -98,7 +109,7 @@ public: testcase (value); Json::Value params; - params["passphrase"] = value; + params[jss::passphrase] = value; testSecretWallet (params, secp256k1_strings); } @@ -111,10 +122,32 @@ public: testLegacyPassphrase (secp256k1_strings.master_seed_hex); } + void testKeyType (char const* keyType, key_strings const& strings) + { + testcase (keyType); + + Json::Value params; + params[jss::key_type] = keyType; + params[jss::passphrase] = common::passphrase; + + testSecretWallet (params, strings); + + params[jss::seed] = strings.master_seed; + + // Secret fields are mutually exclusive. + expect (contains_error (walletPropose (params))); + + params.removeMember (jss::passphrase); + + testSecretWallet (params, strings); + } + void run() { testRandomWallet(); testLegacyPassphrase(); + testKeyType ("secp256k1", secp256k1_strings); + testKeyType ("ed25519", ed25519_strings); } }; @@ -151,7 +184,7 @@ public: testcase (value); Json::Value params; - params["secret"] = value; + params[jss::secret] = value; testSecretWallet (params, secp256k1_strings); } @@ -164,10 +197,50 @@ public: testLegacySecret (secp256k1_strings.master_seed_hex); } + void testInvalidKeyType (char const* keyType) + { + testcase (keyType); + + Json::Value params; + params[jss::key_type] = keyType; + params[jss::passphrase] = common::passphrase; + + Json::Value error; + keypairForSignature (params, error); + + expect (contains_error (error)); + } + + void testKeyType (char const* keyType, key_strings const& strings) + { + testcase (keyType); + + Json::Value params; + params[jss::key_type] = keyType; + params[jss::passphrase] = common::passphrase; + + testSecretWallet (params, strings); + + params[jss::seed] = strings.master_seed; + + // Secret fields are mutually exclusive. + Json::Value error; + keypairForSignature (params, error); + + expect (contains_error (error)); + + params.removeMember (jss::passphrase); + + testSecretWallet (params, strings); + } + void run() { testEmpty(); testLegacySecret(); + testInvalidKeyType ("caesarsalad"); + testKeyType ("secp256k1", secp256k1_strings); + testKeyType ("ed25519", ed25519_strings); } }; diff --git a/src/ripple/unity/crypto.cpp b/src/ripple/unity/crypto.cpp index b5eca920e..7791a83f5 100644 --- a/src/ripple/unity/crypto.cpp +++ b/src/ripple/unity/crypto.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include