mirror of
https://github.com/XRPLF/rippled.git
synced 2026-08-22 23:00:55 +00:00
Add support for Ed25519 seeds encoded using ripple-lib:
When Ed25519 support was added to ripple-lib, a way to specify whether a seed should be used to derive a "classic" secp256k1 keypair or a "new" Ed25519 keypair was needed, and the requirements were that: 1. previously seeds would, correctly, generate a secp256k1 keypair. 2. users would not have to know about whether the seed was used to generate a secp256k1 or an Ed25519 keypair. To address these requirements, the decision was made to encode the type of key within the seed and a custom encoding was designed. The encoding uses a token type of 1 and prefixes the actual seed with a 2 byte header, selected to ensure that all such keypairs will, when encoded, begin with the string "sEd". This custom encoding is non-standard and was not previously documented; as a result, it is not widely supported and other sofware may treat such keys as invalid. This can make it difficult for users that have stored such a seed to use wallets or other tooling that is not based on ripple-lib. This commit adds support to rippled for automatically detecting and properly handling such seeds.
This commit is contained in:
@@ -1400,7 +1400,6 @@ else ()
|
||||
subdir: crypto
|
||||
#]===============================]
|
||||
src/ripple/crypto/impl/GenerateDeterministicKey.cpp
|
||||
src/ripple/crypto/impl/KeyType.cpp
|
||||
src/ripple/crypto/impl/RFC1751.cpp
|
||||
src/ripple/crypto/impl/csprng.cpp
|
||||
src/ripple/crypto/impl/ec_key.cpp
|
||||
|
||||
@@ -21,21 +21,41 @@
|
||||
#define RIPPLE_CRYPTO_KEYTYPE_H_INCLUDED
|
||||
|
||||
#include <string>
|
||||
#include <boost/optional.hpp>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
enum class KeyType
|
||||
{
|
||||
invalid = -1,
|
||||
unknown = invalid,
|
||||
|
||||
secp256k1 = 0,
|
||||
ed25519 = 1,
|
||||
};
|
||||
|
||||
KeyType keyTypeFromString (std::string const& s);
|
||||
inline
|
||||
boost::optional<KeyType>
|
||||
keyTypeFromString (std::string const& s)
|
||||
{
|
||||
if (s == "secp256k1")
|
||||
return KeyType::secp256k1;
|
||||
|
||||
const char* to_string (KeyType type);
|
||||
if (s == "ed25519")
|
||||
return KeyType::ed25519;
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
inline
|
||||
char const*
|
||||
to_string (KeyType type)
|
||||
{
|
||||
if (type == KeyType::secp256k1)
|
||||
return "secp256k1";
|
||||
|
||||
if (type == KeyType::ed25519)
|
||||
return "ed25519";
|
||||
|
||||
return "INVALID";
|
||||
}
|
||||
|
||||
template <class Stream>
|
||||
inline
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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 <ripple/crypto/KeyType.h>
|
||||
|
||||
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";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -70,9 +70,9 @@ Json::Value doWalletPropose (RPC::Context& context)
|
||||
|
||||
Json::Value walletPropose (Json::Value const& params)
|
||||
{
|
||||
boost::optional<KeyType> keyType;
|
||||
boost::optional<Seed> seed;
|
||||
|
||||
KeyType keyType = KeyType::secp256k1;
|
||||
bool rippleLibSeed = false;
|
||||
|
||||
if (params.isMember (jss::key_type))
|
||||
{
|
||||
@@ -85,25 +85,55 @@ Json::Value walletPropose (Json::Value const& params)
|
||||
keyType = keyTypeFromString (
|
||||
params[jss::key_type].asString());
|
||||
|
||||
if (keyType == KeyType::invalid)
|
||||
if (!keyType)
|
||||
return rpcError(rpcINVALID_PARAMS);
|
||||
}
|
||||
|
||||
if (params.isMember (jss::passphrase) ||
|
||||
params.isMember (jss::seed) ||
|
||||
params.isMember (jss::seed_hex))
|
||||
// ripple-lib encodes seed used to generate an Ed25519 wallet in a
|
||||
// non-standard way. While we never encode seeds that way, we try
|
||||
// to detect such keys to avoid user confusion.
|
||||
{
|
||||
Json::Value err;
|
||||
seed = RPC::getSeedFromRPC (params, err);
|
||||
if (!seed)
|
||||
return err;
|
||||
}
|
||||
else
|
||||
{
|
||||
seed = randomSeed ();
|
||||
if (params.isMember(jss::passphrase))
|
||||
seed = RPC::parseRippleLibSeed(params[jss::passphrase]);
|
||||
else if (params.isMember(jss::seed))
|
||||
seed = RPC::parseRippleLibSeed(params[jss::seed]);
|
||||
|
||||
if(seed)
|
||||
{
|
||||
rippleLibSeed = true;
|
||||
|
||||
// If the user *explicitly* requests a key type other than
|
||||
// Ed25519 we return an error.
|
||||
if (keyType.value_or(KeyType::ed25519) != KeyType::ed25519)
|
||||
return rpcError(rpcBAD_SEED);
|
||||
|
||||
keyType = KeyType::ed25519;
|
||||
}
|
||||
}
|
||||
|
||||
auto const publicKey = generateKeyPair (keyType, *seed).first;
|
||||
if (!seed)
|
||||
{
|
||||
if (params.isMember(jss::passphrase) ||
|
||||
params.isMember(jss::seed) ||
|
||||
params.isMember(jss::seed_hex))
|
||||
{
|
||||
Json::Value err;
|
||||
|
||||
seed = RPC::getSeedFromRPC(params, err);
|
||||
|
||||
if (!seed)
|
||||
return err;
|
||||
}
|
||||
else
|
||||
{
|
||||
seed = randomSeed();
|
||||
}
|
||||
}
|
||||
|
||||
if (!keyType)
|
||||
keyType = KeyType::secp256k1;
|
||||
|
||||
auto const publicKey = generateKeyPair (*keyType, *seed).first;
|
||||
|
||||
Json::Value obj (Json::objectValue);
|
||||
|
||||
@@ -116,13 +146,13 @@ Json::Value walletPropose (Json::Value const& params)
|
||||
obj[jss::master_key] = seed1751;
|
||||
obj[jss::account_id] = toBase58(calcAccountID(publicKey));
|
||||
obj[jss::public_key] = toBase58(TokenType::AccountPublic, publicKey);
|
||||
obj[jss::key_type] = to_string (keyType);
|
||||
obj[jss::key_type] = to_string (*keyType);
|
||||
obj[jss::public_key_hex] = strHex (publicKey);
|
||||
|
||||
// If a passphrase was specified, and it was hashed and used as a seed
|
||||
// run a quick entropy check and add an appropriate warning, because
|
||||
// "brain wallets" can be easily attacked.
|
||||
if (params.isMember (jss::passphrase))
|
||||
if (!rippleLibSeed && params.isMember (jss::passphrase))
|
||||
{
|
||||
auto const passphrase = params[jss::passphrase].asString();
|
||||
|
||||
|
||||
@@ -516,6 +516,25 @@ readLimitField(unsigned int& limit, Tuning::LimitRange const& range,
|
||||
return boost::none;
|
||||
}
|
||||
|
||||
boost::optional<Seed>
|
||||
parseRippleLibSeed(Json::Value const& value)
|
||||
{
|
||||
// ripple-lib encodes seed used to generate an Ed25519 wallet in a
|
||||
// non-standard way. While rippled never encode seeds that way, we
|
||||
// try to detect such keys to avoid user confusion.
|
||||
if (!value.isString())
|
||||
return boost::none;
|
||||
|
||||
auto const result = decodeBase58Token(value.asString(), TokenType::None);
|
||||
|
||||
if (result.size() == 18 &&
|
||||
static_cast<std::uint8_t>(result[0]) == std::uint8_t(0xE1) &&
|
||||
static_cast<std::uint8_t>(result[1]) == std::uint8_t(0x4B))
|
||||
return Seed(makeSlice(result.substr(2)));
|
||||
|
||||
return boost::none;
|
||||
}
|
||||
|
||||
boost::optional<Seed>
|
||||
getSeedFromRPC(Json::Value const& params, Json::Value& error)
|
||||
{
|
||||
@@ -623,7 +642,7 @@ keypairForSignature(Json::Value const& params, Json::Value& error)
|
||||
return { };
|
||||
}
|
||||
|
||||
KeyType keyType = KeyType::secp256k1;
|
||||
boost::optional<KeyType> keyType;
|
||||
boost::optional<Seed> seed;
|
||||
|
||||
if (has_key_type)
|
||||
@@ -635,10 +654,9 @@ keypairForSignature(Json::Value const& params, Json::Value& error)
|
||||
return { };
|
||||
}
|
||||
|
||||
keyType = keyTypeFromString (
|
||||
params[jss::key_type].asString());
|
||||
keyType = keyTypeFromString(params[jss::key_type].asString());
|
||||
|
||||
if (keyType == KeyType::invalid)
|
||||
if (!keyType)
|
||||
{
|
||||
error = RPC::invalid_field_error(jss::key_type);
|
||||
return { };
|
||||
@@ -651,20 +669,47 @@ keypairForSignature(Json::Value const& params, Json::Value& error)
|
||||
std::string(jss::key_type) + " is used.");
|
||||
return { };
|
||||
}
|
||||
|
||||
seed = getSeedFromRPC (params, error);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (! params[jss::secret].isString())
|
||||
{
|
||||
error = RPC::expected_field_error (
|
||||
jss::secret, "string");
|
||||
return { };
|
||||
}
|
||||
|
||||
seed = parseGenericSeed (
|
||||
params[jss::secret].asString ());
|
||||
// ripple-lib encodes seed used to generate an Ed25519 wallet in a
|
||||
// non-standard way. While we never encode seeds that way, we try
|
||||
// to detect such keys to avoid user confusion.
|
||||
if (secretType != jss::seed_hex.c_str())
|
||||
{
|
||||
seed = RPC::parseRippleLibSeed(params[secretType]);
|
||||
|
||||
if (seed)
|
||||
{
|
||||
// If the user passed in an Ed25519 seed but *explicitly*
|
||||
// requested another key type, return an error.
|
||||
if (keyType.value_or(KeyType::ed25519) != KeyType::ed25519)
|
||||
{
|
||||
error = RPC::make_error (rpcBAD_SEED,
|
||||
"Specified seed is for an Ed25519 wallet.");
|
||||
return { };
|
||||
}
|
||||
|
||||
keyType = KeyType::ed25519;
|
||||
}
|
||||
}
|
||||
|
||||
if (!keyType)
|
||||
keyType = KeyType::secp256k1;
|
||||
|
||||
if (!seed)
|
||||
{
|
||||
if (has_key_type)
|
||||
seed = getSeedFromRPC(params, error);
|
||||
else
|
||||
{
|
||||
if (!params[jss::secret].isString())
|
||||
{
|
||||
error = RPC::expected_field_error(jss::secret, "string");
|
||||
return {};
|
||||
}
|
||||
|
||||
seed = parseGenericSeed(params[jss::secret].asString());
|
||||
}
|
||||
}
|
||||
|
||||
if (!seed)
|
||||
@@ -672,7 +717,7 @@ keypairForSignature(Json::Value const& params, Json::Value& error)
|
||||
if (!contains_error (error))
|
||||
{
|
||||
error = RPC::make_error (rpcBAD_SEED,
|
||||
RPC::invalid_field_message (secretType));
|
||||
RPC::invalid_field_message (secretType));
|
||||
}
|
||||
|
||||
return { };
|
||||
@@ -681,7 +726,7 @@ keypairForSignature(Json::Value const& params, Json::Value& error)
|
||||
if (keyType != KeyType::secp256k1 && keyType != KeyType::ed25519)
|
||||
LogicError ("keypairForSignature: invalid key type");
|
||||
|
||||
return generateKeyPair (keyType, *seed);
|
||||
return generateKeyPair (*keyType, *seed);
|
||||
}
|
||||
|
||||
std::pair<RPC::Status, LedgerEntryType>
|
||||
|
||||
@@ -112,6 +112,9 @@ readLimitField(unsigned int& limit, Tuning::LimitRange const&, Context const&);
|
||||
boost::optional<Seed>
|
||||
getSeedFromRPC(Json::Value const& params, Json::Value& error);
|
||||
|
||||
boost::optional<Seed>
|
||||
parseRippleLibSeed(Json::Value const& params);
|
||||
|
||||
std::pair<PublicKey, SecretKey>
|
||||
keypairForSignature(Json::Value const& params, Json::Value& error);
|
||||
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
|
||||
#include <ripple/crypto/impl/ec_key.cpp>
|
||||
#include <ripple/crypto/impl/GenerateDeterministicKey.cpp>
|
||||
#include <ripple/crypto/impl/KeyType.cpp>
|
||||
#include <ripple/crypto/impl/openssl.cpp>
|
||||
#include <ripple/crypto/impl/csprng.cpp>
|
||||
#include <ripple/crypto/impl/RFC1751.cpp>
|
||||
|
||||
@@ -107,7 +107,6 @@ public:
|
||||
|
||||
BEAST_EXPECT(! contains_error (result));
|
||||
BEAST_EXPECT(result.isMember (jss::account_id));
|
||||
BEAST_EXPECT(result.isMember (jss::master_key));
|
||||
BEAST_EXPECT(result.isMember (jss::master_seed));
|
||||
BEAST_EXPECT(result.isMember (jss::master_seed_hex));
|
||||
BEAST_EXPECT(result.isMember (jss::public_key));
|
||||
@@ -133,7 +132,6 @@ public:
|
||||
|
||||
BEAST_EXPECT(! contains_error (result));
|
||||
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);
|
||||
@@ -709,6 +707,86 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
void testRippleLibEd25519()
|
||||
{
|
||||
testcase ("ripple-lib encoded Ed25519 keys");
|
||||
|
||||
auto test = [this](char const* seed, char const* addr)
|
||||
{
|
||||
{
|
||||
Json::Value params;
|
||||
Json::Value error;
|
||||
|
||||
params[jss::passphrase] = seed;
|
||||
|
||||
auto ret = keypairForSignature(params, error);
|
||||
|
||||
BEAST_EXPECT(!contains_error(error));
|
||||
BEAST_EXPECT(ret.first.size() != 0);
|
||||
BEAST_EXPECT(toBase58(calcAccountID(ret.first)) == addr);
|
||||
}
|
||||
|
||||
{
|
||||
Json::Value params;
|
||||
Json::Value error;
|
||||
|
||||
params[jss::key_type] = "secp256k1";
|
||||
params[jss::passphrase] = seed;
|
||||
|
||||
auto ret = keypairForSignature(params, error);
|
||||
|
||||
BEAST_EXPECT(contains_error(error));
|
||||
BEAST_EXPECT(error[jss::error_message] ==
|
||||
"Specified seed is for an Ed25519 wallet.");
|
||||
}
|
||||
|
||||
{
|
||||
Json::Value params;
|
||||
Json::Value error;
|
||||
|
||||
params[jss::key_type] = "ed25519";
|
||||
params[jss::seed] = seed;
|
||||
|
||||
auto ret = keypairForSignature(params, error);
|
||||
|
||||
BEAST_EXPECT(!contains_error(error));
|
||||
BEAST_EXPECT(ret.first.size() != 0);
|
||||
BEAST_EXPECT(toBase58(calcAccountID(ret.first)) == addr);
|
||||
}
|
||||
|
||||
{
|
||||
Json::Value params;
|
||||
Json::Value error;
|
||||
|
||||
params[jss::key_type] = "secp256k1";
|
||||
params[jss::seed] = seed;
|
||||
|
||||
auto ret = keypairForSignature(params, error);
|
||||
|
||||
BEAST_EXPECT(contains_error(error));
|
||||
BEAST_EXPECT(error[jss::error_message] ==
|
||||
"Specified seed is for an Ed25519 wallet.");
|
||||
}
|
||||
};
|
||||
|
||||
test("sEdVWZmeUDgQdMEFKTK9kYVX71FKB7o", "r34XnDB2zS11NZ1wKJzpU1mjWExGVugTaQ");
|
||||
test("sEd7zJoVnqg1FxB9EuaHC1AB5UPfHWz", "rDw51qRrBEeMw7Na1Nh79LN7HYZDo7nZFE");
|
||||
test("sEdSxVntbihdLyabbfttMCqsaaucVR9", "rwiyBDfAYegXZyaQcN2L1vAbKRYn2wNFMq");
|
||||
test("sEdSVwJjEXTYCztqDK4JD9WByH3otDX", "rQJ4hZzNGkLQhLtKPCmu1ywEw1ai2vgUJN");
|
||||
test("sEdV3jXjKuUoQTSr1Rb4yw8Kyn9r46U", "rERRw2Pxbau4tevE61V5vZUwD7Rus5Y6vW");
|
||||
test("sEdVeUZjuYT47Uy51FQCnzivsuWyiwB", "rszewT5gRjUgWNEmnfMjvVYzJCkhvWY32i");
|
||||
test("sEd7MHTewdw4tFYeS7rk7XT4qHiA9jH", "rBB2rvnf4ztwjgNhinFXQJ91nAZjkFgR3p");
|
||||
test("sEd7A5jFBSdWbNeKGriQvLr1thBScJh", "rLAXz8Nz7aDivz7PwThsLFqaKrizepNCdA");
|
||||
test("sEdVPU9M2uyzVNT4Yb5Dn4tUtYjbFAw", "rHbHRFPCxD5fnn98TBzsQHJ7SsRq7eHkRj");
|
||||
test("sEdVfF2zhAmS8gfMYzJ4yWBMeR4BZKc", "r9PsneKHcAE7kUfiTixomM5Mnwi28tCc7h");
|
||||
test("sEdTjRtcsQkwthDXUSLi9DHNyJcR8GW", "rM4soF4XS3wZrmLurvE6ZmudG16Lk5Dur5");
|
||||
test("sEdVNKeu1Lhpfh7Nf6tRDbxnmMyZ4Dv", "r4ZwJxq6FDtWjapDtCGhjG6mtNm1nWdJcD");
|
||||
test("sEd7bK4gf5BHJ1WbaEWx8pKMA9MLHpC", "rD6tnn51m4o1uXeEK9CFrZ3HR7DcFhiYnp");
|
||||
test("sEd7jCh3ppnQMsLdGcZ6TZayZaHhBLg", "rTcBkiRQ1EfFQ4FCCwqXNHpn1yUTAACkj");
|
||||
test("sEdTFJezurQwSJAbkLygj2gQXBut2wh", "rnXaMacNbRwcJddbbPbqdcpSUQcfzFmrR8");
|
||||
test("sEdSWajfQAAWFuDvVZF3AiGucReByLt", "rBJtow6V3GTdsWMamrxetRDwWs6wwTxcKa");
|
||||
}
|
||||
|
||||
void run() override
|
||||
{
|
||||
testKeyType (boost::none, secp256k1_strings);
|
||||
@@ -721,6 +799,9 @@ public:
|
||||
testKeypairForSignature (std::string("secp256k1"), secp256k1_strings);
|
||||
testKeypairForSignature (std::string("ed25519"), ed25519_strings);
|
||||
testKeypairForSignature (std::string("secp256k1"), strong_brain_strings);
|
||||
|
||||
testRippleLibEd25519();
|
||||
|
||||
testKeypairForSignatureErrors ();
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user