Skip to content
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

correct HDWallet derivation path for EOS keys #7

Closed
seitau opened this issue Apr 23, 2018 · 11 comments
Closed

correct HDWallet derivation path for EOS keys #7

seitau opened this issue Apr 23, 2018 · 11 comments
Assignees
Labels

Comments

@seitau
Copy link

seitau commented Apr 23, 2018

I would like to know the correct derivation path in order to derive EOS keys from mnemonic phrases. Now I am referring to SatoshiLab's list of coin types for BIP44. Please let me know if it is wrong.

@jcalfee jcalfee self-assigned this Apr 23, 2018
@jcalfee
Copy link
Contributor

jcalfee commented Apr 23, 2018

fyi: Some work on the signatures has been done. Xeroc had a un-merged patch for Trezor and Bitshares the HW wallet that produced passing signatures. I don't see the patch now. But I do see a new xeroc PR for steem: trezor/trezor-mcu#121

For BIP44, EOS is different. It does not have change addresses. Change addresses in BIP44 are fine but for contract permissions that are not a good fit. Due to the elliptic curve math derived keys on the same hierarchical level have a weaker security profile that make them unsafe. For example, one private key and a chain code can revile all private keys at that level and lower levels. So, this is better suited for flat and redundant change addresses but not suited for more meaningful contract permissions.

Deriving keys are done in binary and use only sha256 hashing that does not involve any elliptic curve math. So, while EOS needs something different it is actually easier to implement and understand.

This will be the starting point:

mnemonicSeedBuffer = bip39.mnemonicToSeed(mnemonic, passphrase)
// new stuff I propose (this needs peer review):
chainCode = Buffer.from('00'.repeat(32), hex) // testnet is '00'.repeat(32), replace with real chainCode
MASTER_SALT = chainCode
chainSeedBuffer = createHmac('sha256', MASTER_SALT).update(mnemonicSeedBuffer).digest()

// Here is what we have implemented now for the front-end and endorsed by @bytemaster back in 2017.. 
// This is more standard and implemented in https://github.com/eosio/eosjs-keygen
owner = createHash('sha256').update(chainSeedBuffer).update('owner').digest() // root of everything
active = createHash('sha256').update(owner).update('active').digest() // active always under owner
custom1 = createHash('sha256').update(active).update('custom1').digest() //custom permissions are always under active
custom2 = createHash('sha256').update(active).update('custom2').digest() // custom permissions can be flat
custom2a = = createHash('sha256').update(custom2).update('custom2a').digest() // or nested

// the numbering in the custom variable name is just for illustration

If you go digging into eosjs-keygen, you'll find unit test and up-to-date markdown docks illustrating and enforcing the rules I showed above. If you wish to make it compatible, the string from of the master key has a two letter prefix "PW" followed by a WIF key; the prefix is just cosmetic and can be removed and will play no role in hashing or deriving keys. So, for a given mnemonic and passphrase you could create the PW prefixed master key used to derive the same chainSeedBuffer data and the same derived keys (the mnemonic comes first then a compatible master password could be create). This use case is limited though because the master password is typically not cold storage but the mnemonic is.

https://github.com/EOSIO/eosjs-keygen

# See generateMasterKeys - https://github.com/EOSIO/eosjs-keygen/blob/v1.3.1/src/keygen.js#L34-L59
master = PrivateKey.fromSeed('test') // <- returns eosjs-ecc key_private.js
ownerPrivate = master.getChildKey('owner')
activePrivate = ownerPrivate.getChildKey('active')
customPrivate = activePrivate.getChildKey('custom') // custom contract permissions

In getChildKey, I did leave a warning until I see it used in the eosio wallet:

    function getChildKey(name) {
      // console.error('WARNING: getChildKey untested against eosd'); // no eosd impl yet
      const index = createHash('sha256').update(toBuffer()).update(name).digest()
      return PrivateKey(index)
    }

@jcalfee jcalfee added the help label Apr 23, 2018
@seitau
Copy link
Author

seitau commented Apr 23, 2018

@jcalfee I really appreciate your detailed explanation. This is of great help to me :)

@seitau seitau closed this as completed Apr 23, 2018
@seitau
Copy link
Author

seitau commented Apr 24, 2018

I got one more question: In which level of public key should be used for registering in EOS distribution smart contract? For example, which permission group is used at generate EOS keys in eos.io?

Also, how can we derive the real chainCode used as MASTER_SALT for mainnet? Is there any standardized way to derive it?

@seitau seitau reopened this Apr 24, 2018
@jcalfee
Copy link
Contributor

jcalfee commented Apr 24, 2018

Generate EOS Key at eos.io is non-deterministic (completely random).

@dskvr can you please update us on how a EOS claim will happen (does this still need an account name for example)?

You can probably setup a deterministic claim key by using a MASTER_SALT that includes the chain ID concatenated with some words 'EOS Claim Key' and something else to make the claim key unique for the user for each ETH account they are claiming (so the ETH address):

MASTER_SALT = chainId + 'EOS Claim Key' + ethAddress

The chainCode / MASTER_SALT will probably be a hash of the genesis block (all the initial balances, etc). It will not be available until the chain is live.

@jcalfee
Copy link
Contributor

jcalfee commented Apr 30, 2018

For "Generate EOS Key" aka (EOS Claim Key), normalization is as follows:

  • chainId (all lowercase hex string)
  • 'EOS Claim Key' case sensitive string concatenation
  • ethAddress - Validate the ETH Address ensuring it starts with 0x and that the checksum matches. If the address is valid, use this as the 3rd and final concatenation and proceed with the MASTER_SALT. If the ETH Address is not valid but appears to be a valid address (the right length and character set), an attempt to fix the address may be offered. Warn and prompt the user that this ETH Address could not be validated and ask to proceed anyways. The software should then convert the ETH Address into its mix-case format and ensure a 0x prefix exists or is added then concat the mix cased format as a string to create the MASTER_SALT.

@seitau
Copy link
Author

seitau commented May 7, 2018

Thanks to your help, I could successfully derived my eos pub/priv key from my mnemonic 👍.
Thank you very much!

@crazybits
Copy link

Slip48 is recommended

https://github.com/satoshilabs/slips/blob/master/slip-0048.md

@jcalfee
Copy link
Contributor

jcalfee commented Sep 4, 2018

@crazybits Sip48 does not cover role hierarchies .. Where active is a child of owner. Although role hierarchies can be completely ignored in key derivation if you want too. What do you think about the role hierarchies in EOS?

It is a way to introduce a non-hardened path (without the Sip48 tick ') but with strings and no public chain code support. So if you have the owner private you can create the active private and any more specific children of the active key. That would allow string based roles to be created outside of cold storage that were sill recoverable without having to go back to cold storage and add it to the backup for example.

@jcalfee
Copy link
Contributor

jcalfee commented Sep 4, 2018

Please don't get carried away with this one. I should mention though that the ECC math used for public / private chain codes did work for me when I used large numbers like arbitrary role names in EOS .. It is the spec that locks it down to numbers not the math .. It is actually just hashing the role ID to get the public or private key. Using numbers allows for better discovery without an index and tends to drive people to go register and use well known roles though. It is in my mind that strings can be in a modified BIP-0044/SLIP-0044 if it makes for a better use-case.

@c1pca
Copy link

c1pca commented Sep 8, 2018

As far as I know Ledger uses BIP-0044/SLIP-0044 ("m/44'/194'/0'/0/0").

@shikhars371
Copy link

Thanks to your help, I could successfully derived my eos pub/priv key from my mnemonic 👍.
Thank you very much!

hello sir, can you give me some reference on creating EOS active and owner keys by the help of mnemonic 12 words seed? please help me im stuck in this from past 4 days but no luck please help me

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

5 participants