Skip to content

Commit

Permalink
Add support for Ed25519 seeds encoded using ripple-lib:
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
nbougalis committed Nov 6, 2018
1 parent 77462b8 commit 513b1dd
Show file tree
Hide file tree
Showing 8 changed files with 216 additions and 78 deletions.
1 change: 0 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
30 changes: 25 additions & 5 deletions src/ripple/crypto/KeyType.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;

if (s == "ed25519")
return KeyType::ed25519;

return {};
}

const char* to_string (KeyType type);
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
Expand Down
39 changes: 0 additions & 39 deletions src/ripple/crypto/impl/KeyType.cpp

This file was deleted.

60 changes: 45 additions & 15 deletions src/ripple/rpc/handlers/WalletPropose.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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))
{
Expand All @@ -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;
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;
}
}
else

if (!seed)
{
seed = randomSeed ();
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();
}
}

auto const publicKey = generateKeyPair (keyType, *seed).first;
if (!keyType)
keyType = KeyType::secp256k1;

auto const publicKey = generateKeyPair (*keyType, *seed).first;

Json::Value obj (Json::objectValue);

Expand All @@ -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();

Expand Down
75 changes: 60 additions & 15 deletions src/ripple/rpc/impl/RPCHelpers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down Expand Up @@ -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)
Expand All @@ -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 { };
Expand All @@ -651,28 +669,55 @@ keypairForSignature(Json::Value const& params, Json::Value& error)
std::string(jss::key_type) + " is used.");
return { };
}

seed = getSeedFromRPC (params, error);
}
else

// 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())
{
if (! params[jss::secret].isString())
seed = RPC::parseRippleLibSeed(params[secretType]);

if (seed)
{
error = RPC::expected_field_error (
jss::secret, "string");
return { };
// 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 ());
seed = parseGenericSeed(params[jss::secret].asString());
}
}

if (!seed)
{
if (!contains_error (error))
{
error = RPC::make_error (rpcBAD_SEED,
RPC::invalid_field_message (secretType));
RPC::invalid_field_message (secretType));
}

return { };
Expand All @@ -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>
Expand Down
3 changes: 3 additions & 0 deletions src/ripple/rpc/impl/RPCHelpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
1 change: 0 additions & 1 deletion src/ripple/unity/crypto.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
Loading

0 comments on commit 513b1dd

Please sign in to comment.