Open litt3 opened 1 year ago
@mehcode Yes, JS not allowing function overrides makes this harder. I wasn't aware of that limitation.
toStandard[Ed25519|Ecdsa]PrivateKey(passphrase="", index=0)
, such that the returned key is at derivation path m/44'/3030'/0'/0'/index'
for Ed25519 keys, and m/44'/3030'/0'/0/index
for ECDSA keys.
to[Ed25519|Ecdsa]PrivateKey(passphrase = "", path = ...)
to[Ed25519|Ecdsa]PrivateKeyAtPath(passphrase, path)
or similarThoughts?
@alittley I've reviewed your proposition. We were discussing at the issue as well that JS does not allow overrides. Some notes here:
to[Ed25519|Ecdsa]PrivateKeyAtPath(passphrase, path)
(for existing ones)toStandard[Ed25519|Ecdsa]PrivateKey(passphrase="", index=0)
method.PrivateKey.legacyDerive(index)
@ochikov Concerning explicit paths: toStandard[Ed25519|Ecdsa]PrivateKey(passphrase="", index=0)
cannot support them as written above, since it is only accepting a single index
. A full fledged derivation path would have to accept a path
Now, concerning legacy key derivation: there are subtle differences in how the SDKs handle this currently. Rather than try to detail all the differences, I'm just going to start a proposal for how it can be standardized.
mnemonic.toLegacyPrivateKey()
returns an Ed25519 key
isLegacy
flag, or to infer isLegacy
based on word count
isLegacy
actually means "is legacy v1", since legacy v2 mnemonics are notably not isLegacy
toLegacyPrivateKey
, rather than inconsistently and imperfectly inferring isLegacy
. I am in favor of deprecating all functions that accept an isLegacy
argument, and removing the isLegacy
member from mnemonic classesPrivateKey.legacyDerive(index)
returns the child key at index
legacyDerive
function valid for v1 legacy, v2 legacy, or both? It's not documented as far as I can tell.Some other things to do with legacy keys that need addressing:
MNEMONIC3_STRING
is an unacceptable name. There should be legacyV1MnemonicString
, and legacyV2MnemonicString
, or similarly descriptive namesBefore this goes into effect, we will need to resolve the ECDSA naming discussion. Should we specify the secp256k1
curve in newly created functions, or stick with the existing ambiguous naming?
Argument for excluding the curve from new function names:
ECDSA
without specifying curveArgument for including curve in new function names:
ECDSA
problematic@alittley
During the implementation of the test vectors for SLIP-10, BIP-39 and BIP-32, we've discovered that we do not support the generation of the xpub
and xprv
keys menitoned here inside the BIP-32 specs. As far as I understand, BIP-32 is basically the standard upon which we derive ECDSA child keys for HD(Hierarchical Deterministic) wallets based on the BIP-39 standard? Can you verify if this is true?
I hadn't noticed that the BIP-32 test vectors relied on an encoding we don't have 🤔 I think it's ok for now that the BIP-32 test vectors aren't implemented, since SLIP10 provides alternative vectors for Ed25519 and ECDSA_secp256k1, which don't rely on that extended key encoding.
@petreze @alittley Guys, do you think that we can start implementing the refactoring soon? What about the curve into the name of the functions?
@ochikov I see the following things that must be completed:
1/10/2023
ECDSAk256
or ECDSAsecp256k1
(to be voted on)1/23/2023
Majority vote: ECDSAsecp256k1
for naming convention
Answer to the questions from the first post:
PrivateKey.legacyDerived
is used for both v1 and v2 legacy derivation. There are tests in all of the SDKs (Java, JS, Go) that use the method. In Java they are:
I consider that isLegacy
can be removed
Additional questions:
ECDSA
to ECDSAsecp256k1
only in the new function toStandardECDSAsecp256k1PrivateKey
or change all instances of ECDSA
(methods and classes)?toHardenedIndex()
method or do we expect the users to harden the indexes themselves if they need to? toStandardECDSAsecp256k1PrivateKey("", 2147483648)
vs toStandardECDSAsecp256k1PrivateKey("", toHardenedIndex(0)
. If it is needed, should it be in the PrivateKeyECDSA
, the Mnemonic
or a Utils
class?Mnemonic.toLegacyPrivateKey
? In Java it uses the number of words to determine if the private key should be v1 or v2. In JS it uses the isLegacy
flag. Also do we need to change the implementation or create a new method, toStandardLegacyPrivateKey
, which extracts the private key from the mnemonic and then derives the correct path m/44'/3030'/0'/0'/index'
?@alittley Did you have the chance to look at the above comment? We are almost ready with the implementation in JAVA and those questions come across during the implementation.
@ochikov
bip32
or bip32
utils class exists, it would make sense to put it there, since "hardening" is a concept defined in BIP 32. Otherwise, a utils class seems appropriate. IMO this utility function does not need to be standardized across SDKsMnemonic.toLegacyPrivateKey
could (but shouldn't necessarily have to) be changed, to make the implementation more clear, or improve internal organization. Of course, care should be taken to not modify the external behavior of this function. I would not create a new toStandardLegacyPrivateKey
method, though. Since the mnemonic -> key
derivation is already non-standard, using the "correct" derivation path won't make the end result any more "standard."@alittley Can you check the PR in the Java SDK? Do we have to change or add anything else?
@alittley We propose to remove the default parameter values from toStandard()
methods because it is not possible to do it in Go.
We also provide test vectors for legacyV1/V2 and toStandard derivations:
Mnemonic: jolly kidnap tom lawn drunk chick optic lust mutter mole bride galley dense member sage neural widow decide curb aboard margin manure
Mnemonic: obvious favorite remain caution remove laptop base vacant increase video erase pass sniff sausage knock grid argue salt romance way alone fever slush dune
Mnemonic: inmate flip alley wear offer often piece magnet surge toddler submit right radio absent pear floor belt raven price stove replace reduce plate home
Mnemonic: inmate flip alley wear offer often piece magnet surge toddler submit right radio absent pear floor belt raven price stove replace reduce plate home
Just a note, you have the 4th index in the derivation chain for the ECDSAsecp256k1 test vectors as hardened, when in fact they are not. I derived everything correctly as you have when using the m/44'/3030'/0'/0/index
path.
@rwalworth Thank you for noticing. I've edited my comment
I came up with some additional 12 word mnemonic test vectors for ECDSAsecp256k1 and ED25519 private key derivation that should also be added to our internal "standard" test vectors. These test vectors are only for our standard derivation.
Mnemonic: finish furnace tomorrow wine mass goose festival air palm easy region guilt
Chain m/44'/3030'/0'/0'/0'
Chain m/44'/3030'/0'/0'/2147483647'
Chain m/44'/3030’/0’/0’/0’; Passphrase: “some pass”
Chain m/44'/3030'/0'/0'/2147483647’; Passphrase: “some pass”
Mnemonic: finish furnace tomorrow wine mass goose festival air palm easy region guilt
Chain m/44'/3030'/0'/0/0
Chain m/44'/3030’/0’/0/0’
Chain m/44'/3030’/0’/0/0; Passphrase: “some pass”
Chain m/44'/3030’/0’/0/0’; Passphrase: “some pass”
Chain m/44'/3030’/0’/0/2147483647; Passphrase: “some pass”
Chain m/44'/3030’/0’/0/2147483647’; Passphrase: “some pass”
There are problems with how key derivations are currently implemented. This issue is for discussing the best solution to the existing problems, and settling on a course of action.
Background
Proposal (will be updated as discussion occurs)
Converting Mnemonics to Private Keys
Mnemonic.toStandardEd25519PrivateKey(passphrase="", index=0)
m/44'/3030'/0'/0'/index'
index
. Passing in a pre-hardened index should failMnemonic.toStandardECDSAsecp256k1PrivateKey(passphrase="", index=0)
m/44'/3030'/0'/0/index
index
Mnemonic.toLegacyPrivateKey()
returns anEd25519
private keyDeriving Child Keys
PrivateKey.derive(index)
index
PrivateKey
is an Ed25519 key,index
is automatically hardened. passing in a pre-hardened index should failPrivateKey
is an ECDSA key,index
must be manually hardened, if desiredPrivateKey.legacyDerive(index)
index
, using the legacy algorithmDeprecated Functions (including but not necessarily limited to)
Mnemonic.toEd25519PrivateKey(passphrase = "", path = ...)
Mnemonic.toECDSAPrivateKey(passphrase = "", path = ...)
Tests
legacyMnemonicV1
legacyMnemonicV2
toStandardEd25519PrivateKey
0
0
max
toStandardECDSAsecp256k1PrivateKey
0
0'
0
0'
max
max'
Possible future functionality (not included in the current proposal)
Questions
PrivateKey.legacyDerive(index)
actually needed? Is this just for one of the legacy versions, or both?isLegacy
flag fromMnemonic
?isLegacy
flag, or to inferisLegacy
based on word countisLegacy
actually seems to mean "is legacy v1", since legacy v2 mnemonics are notably notisLegacy
toLegacyPrivateKey
, rather than inconsistently and imperfectly inferringisLegacy