-
Notifications
You must be signed in to change notification settings - Fork 1.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add support for Ed25519 seeds encoded using ripple-lib: #2734
Conversation
|
||
Json::Value obj (Json::objectValue); | ||
|
||
auto const seed1751 = seedAs1751 (*seed); | ||
auto const seedHex = strHex (*seed); | ||
auto const seedBase58 = toBase58 (*seed); | ||
|
||
obj[jss::master_seed] = seedBase58; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMPORTANT THING TO POINT OUT:
While we parse the ripple-lib
encoded Ed25519 key correctly, we return the "canonical" seed encoding. Should that be changed? And, more generally, should we just accept that that an encoded seed represents the key type and the seed used to generate the wallet?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems we should return an error upon detecting a type mismatch especially since we already reject upon an invalid keyType
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We do. Look at line 102 in this same file.
@intelliot
You mentioned some thought out bip for this at some time iirc, for use with
destination tags?
|
That’s coming sometime this week. A new seed format could also be done at that time. But the current must be supported anyways. |
Haha, my bad :/
|
Maybe u cld migrate ppl to new format using js tools? Rather than have to
keep supporting?
|
|
// 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(seed = RPC::parseRippleLibSeed(params)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if(seed = RPC::parseRippleLibSeed(params)) | |
if ((seed = RPC::parseRippleLibSeed(params))) |
Assignments in if
statements that don't declare variables always make me look twice. I think the convention is to add redundent parens to signal "yes, I really meant =
and not ==
."
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, yeah. It’s sketchy even with the double parens. I’ll rewrite.
@@ -111,7 +111,6 @@ Handler const handlerArray[] { | |||
{ "tx_history", byRef (&doTxHistory), Role::USER, NO_CONDITION }, | |||
{ "unl_list", byRef (&doUnlList), Role::ADMIN, NO_CONDITION }, | |||
{ "validation_create", byRef (&doValidationCreate), Role::ADMIN, NO_CONDITION }, | |||
{ "validation_seed", byRef (&doValidationSeed), Role::ADMIN, NO_CONDITION }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd probably split the changes in this commit into two commits: 1) Handle ripple-lib encoded seeds, 2) Remove "validation_seed" command.
auto const seedHex = strHex (*seed); | ||
auto const seedBase58 = toBase58 (*seed); | ||
|
||
obj[jss::master_seed] = seedBase58; | ||
obj[jss::master_seed_hex] = seedHex; | ||
obj[jss::master_key] = seed1751; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We'll need to document that we're removing this field.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll just re-add it. It's pointless to remove it at this point.
src/ripple/rpc/impl/RPCHelpers.cpp
Outdated
return boost::none; | ||
}; | ||
|
||
if (params.isMember(jss::passphrase) && !params.isMember(jss::seed)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we want to do an entropy check on the passphrase like we do for non-ripple-lib encoded passphrases?
Edit: also see comment above about using passphrases; I'm not sure we should be checking this at all.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't we need to check jss::secret
?
Edit: keypairForSignature
needs to check this, WalletPropose
does not. Consider having the caller find the correct field the secret is contained in and just pass in either the std::string
to decode or Json::Value
to the parseRippleLibSeed
function. i.e pretty much what the decode
lambda does now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ugh... 😡
src/ripple/rpc/impl/RPCHelpers.cpp
Outdated
// 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(seed = RPC::parseRippleLibSeed(params)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if(seed = RPC::parseRippleLibSeed(params)) | |
if ((seed = RPC::parseRippleLibSeed(params))) |
Add redundant params arround assignment in if
when not declaring a new variable.
src/ripple/rpc/impl/RPCHelpers.cpp
Outdated
if (!j.isString()) | ||
return boost::none; | ||
|
||
auto const result = decodeBase58Token(j.asString(), TokenType::None); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A passphrase isn't going to be base58 encoded. Shouldn't this use a different decode for passphrases?
Edit: We derive the seed from a passphrase in two ways: 1) we hash it; 2) we use rfc1715 to get a 128 bit seed. I don't see how either of those can be used to add additional encoding to specify a ripple lib seed. So I don't think we should use a passphrase here at all.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Several codepaths use “passphrase” as the catch-all (e.g. wallet_propose
) and we make an effort to determine what this thing is.
I tried very carefully to keep the semantics sane and in line with previous expectations, but I’ll double-check.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're right, I'm looking at ParseGenericSeed
(https://github.com/seelabs/rippled/blob/develop/src/ripple/protocol/impl/Seed.cpp#L95-L130), and GetSeedFromRPC
(https://github.com/seelabs/rippled/blob/develop/src/ripple/rpc/impl/RPCHelpers.cpp#L542-L579) the logic is:
If one of the following are set: passphrase, seed, seed hex, try the following:
- If "seed", parse as base58
- If "passphrase":
a) check if hex, otherwise
b) check if base 58, otherwise
c) use RFC1751, otherwise
d) hash the passphrase - if "seed_hex", set hex exact
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, the logic is, let's be generous and say convoluted. I went through and reverified the logic and I'm convinced the changes do the right thing.
The only "potential" problem is someone that used a passphrase that happens to also be a valid "ripple-lib encoded Ed25519" string. But the chances of that are, essentially, astronomical.
will this change affect Ed25519 seeds generated by other tool? |
Jenkins Build SummaryBuilt from this commit Built at 20181023 - 19:23:46 Test Results
|
@yxxyun: this issue is because The code you link to does not appear to handle base58 encoded keys; it deals with the raw value of the seed, after it has been decoded from its string form. I don't know if that code handles string-encoded seeds at all, thought I assume it does. If it does, it may be useful to at least add detection of these "non-standard" seeds. Perhaps @donovanhide can chime in on this. |
The 'validation_seed' RPC command was used to change the validation key used by a validator at runtime. Its implementation was commented out with commit fa796a2 which has been included in the codebase since the 0.30.0 release and there are no plans to reintroduce the functionality at this point. Validator operators should migrate to using validator manifests instead.
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.
Where is the documentation for validator manifests? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This code is getting messy (not your fault, it was messy already when you added your modifications). I'm going to try to refactor some of this code with the following plan:
- Find the field the secret lives in from a list of candidate fields
- Decode that field accoring to field specific rules (i.e. hex for
key_hex
, longer list forpassphrase
) - If the field can decode as base58, handle both ripple-lib and non-ripple lib cases
- Try the other decoders
I'll try to code that up. We still may want to do that in another PR even if it looks good.
@@ -1,39 +0,0 @@ | |||
//------------------------------------------------------------------------------ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This file needs to be removed from CMakeLists.txt or the non unity build will fail.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Gah, I thought I did this.
|
||
// 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. | ||
{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We already know what field the secret is in. Lines 677-683 can be simplified to:
if (secretType != jss::seed_hex.c_str()){
seed = RPC::parseRippleLibSeed(params[secretType]);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
src/test/rpc/KeyGeneration_test.cpp
Outdated
Json::Value error; | ||
|
||
params[jss::key_type] = "secp256k1"; | ||
params[jss::secret] = seed; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be jss::seed
, not jss::secret
. A jss::secret
is not allowed to set the key type, and this test is testing for a key type mismatch.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
Co-Authored-By: nbougalis <[email protected]>
Here's my take on a refactor: seelabs@034e7b3 Here's the most relevant part: https://github.com/seelabs/rippled/blob/json_seed_refactor/src/ripple/rpc/impl/RPCHelpers.cpp#L546-L729
Also please note I'm not proposing that for the code base. I just wanted to give an idea of what I had in mind (that doesn't even pass the JSONRPC test - most likely because the error messages changed, though I haven't dug into it. It does pass the WalletPropose tests). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
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. | ||
{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I found myself reading this over and over to see the purpose of the limited scope. I finally convinced myself that it has no purpose, and was just left over from the removed if
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wanted group the ripple-lib specific workaround together in one block.
boost::optional<Seed> | ||
parseRippleLibSeed(Json::Value const& value) | ||
{ | ||
// ripple-lib encodes seed used to generate an Ed25519 wallet in a |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"used to"
Seems like it still does?
I disagree with the concept by the way for what it's worth: If a library does something specific/weird that only makes sense within this library, it shouldn't be integrated in the server software but fixed within that library. Otherwise every library can just invent their custom encoding scheme. |
It made sense at the time :) Ripple still published a client with steps to
back up your secret as a nice single base58 string. It was supposed to be
implemented in rippled but it was never gotten around to... till now I
guess :)
|
While I agree that we shouldn't support every encoding ever thought up, the simple fact is that The alternative to this commit is to detect such seeds, when entered, and display a warning message so users understand what is happening. |
Since a canonical format can be generated from non-canonical seeds apparently, this should be done by ripple-lib before communicating with rippled (this function probably shouldn't be available to end users anyways without big fat warnings, as it sends private key material to a trusted server, something that end users shouldn't do). |
It is just making it more difficult for someone to reimplement rippled (especially since I don't find a good description of this custom encoding), but it might make it easier for ripple-lib users that send seeds to a rippled server. |
The problem isn’t that The problem is that it communicates this encoded seed to the user so that they can have a backup. And that encoding will only work with It’s a big surprise and at least a little bit scary (happy Halloween, by the way 🎃) if a seed that I was told to backup to be able to recover my wallet or import it to another one ends up not working when entered into another wallet. |
Just FYI: The "Ledger" hardware wallet (LedgerHQ/ledger-wallet-ripple#5) has a similar or even worse flaw/issue and is probably FAR more widespread than ed25519 keys on ripple-lib (unless they are now the default way to create wallets? Note to self: Get stats on distribution of 25519 vs 256k accounts). |
I know that Ledger uses an incompatible derivation mechanism. However unfortunate, I understand their rationale. At least they don’t display the key formatted to look like a seed. But Ripple is the primary author of Mind you, that it’s my position (speaking only for myself and not ex cathedra) that |
I agree with you on the front of not being able to create keys, I'm kinda unsure about the signing part (signing alone is easy, serializing/encoding the data to be signed is a different matter). If wallet functionality is off limits for That's just a side note though, I just wanted to voice my opinion on adding more seed/key formats to |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fine
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, and the requirements were that:
The solution was to encode the type of key within the seed and a custom encoding was designed. The encoding utilizes a 3-byte prefix for Ed25519 keypairs which results in such keypairs beginning with the string "sEd". The encoding is non-standard and such that other sofware will treat such keys as invalid.
This commit adds support to rippled for automatically detecting and properly handling such seeds.