EOSIO / eosjs-ecc

Elliptic curve cryptography functions: Private Key, Public Key, Signature, AES, Encryption, Decryption
288 stars 119 forks source link

correct HDWallet derivation path for EOS keys #7

Closed seitau closed 6 years ago

seitau commented 6 years ago

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 commented 6 years ago

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: https://github.com/trezor/trezor-mcu/pull/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)
    }
seitau commented 6 years ago

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

seitau commented 6 years ago

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?

jcalfee commented 6 years ago

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 commented 6 years ago

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

seitau commented 6 years ago

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

crazybits commented 6 years ago

Slip48 is recommended

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

jcalfee commented 6 years ago

@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 commented 6 years ago

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 commented 6 years ago

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

shikhars371 commented 4 years ago

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