update for beem: support for BIP39 mnemonic with BIP32 support for derivation path added

in HiveDevs4 years ago (edited)

Repository

https://github.com/holgern/beem


beem-logo

beem is a python library for STEEM and HIVE. The current version is 0.23.4.

There is also a discord channel for beem: https://discord.gg/4HM592V

The newest beem version can be installed by:

pip install -U beem

Check that you are using hive nodes. The following command

beempy updatenodes --hive

updates the nodelist and uses only hive nodes. After setting hive as default_chain, beempy updatenodes can be used without switching to steem.

The list of nodes can be checked with

beempy config

and

beempy currentnode

shows the currently connected node.

Changelog for versions 0.23.4

  • Bip39 and Bip32 support has been added to beempy keygen
  • Privatekey derivation based on Bip39/Bip32 has been added
  • Several unit tests have been added
  • price/market fix for custom nodes (thanks to @crokkon)
  • Replace brain key generation by BIP39 for beempy keygen
  • Remove password based key generation for beempy changekeys
  • Improved yaml header for beempy download

Old-style brainwallets

Using a single SHA256 operation to generate private keys from a simple passphrase to generate private keys is insecure. With a simple passphrase, I mean human-generated passphrases with low entropy.

Using 30 random chars for a password from which the private keys are generated with a single SHA256 operation should be safe for now.

But there is a more secure way which comes with no additionally costs, so why not using it?

Advantages of BIP39 mnemonic with BIP32 support for derivation path

  • key stretching slows down brute force attacks from 100k/s to 1/s
  • consists of 12, 15, 18, 21or 24 words which is equivalent to an entropy strength of 128, 160, 192, 224 or 256 bits
  • mnemonic is created from a word list with 2048 words
  • last word is a checksum word, it is possible to check if the word list is valid
  • it is possible to derive keys for all alt-accounts
  • when a key is leaked (e.g. posting key) it is possible to create a new posting key without the need to change the memonic phrase
  • Can be protected with a passphrase

BIP39

It works as follows:

from beemgraphenebase.account import Mnemonic
from binascii import hexlify, unhexlify
m = Mnemonic()
wordlist = m.generate(strength=128)
print(wordlist)

creates a wordlist with 128 bit of entropy:

museum music dust enlist account arrest evolve inmate rack predict theory section

I can then create a seed from it, protected by a password:

seed = m.to_seed(wordlist, passphrase="beem")
print(hexlify(seed).decode("ascii"))

results in my seed:

5fe585f493e6e1799f262785af4de997d088ef65ad90938d271af0eca977f13a856a5a11e231b1085a27d435a29edca03d3aedea852c912687bc8e225fbc31b3

I can now use the BIP32 standard to create 4 new keys for my account beembot.
As shown in slip-0048, a path for a graphene network consists of

m / purpose' / network' / role' / account-index' / key-index'

The purpose is constant 48', the network and role is shown here:

image.png

So we use 13' for the network slot and for roles

  • 0' for owner
  • 1' for active
  • 3' for memo
  • 4' for posting

The account-index starts at 0 and counts up for new accounts. The key-index starts also with 0 and can be increased when a new key is needed.

The four paths would then be:

  • owner: m/48'/13'/0'/0'/0'
  • active: m/48'/13'/1'/0'/0'
  • posting: m/48'/13'/4'/0'/0'
  • memo: m/48'/13'/3'/0'/0'
from beemgraphenebase.bip32 import BIP32Key, parse_path
from beemgraphenebase.account import PrivateKey

# owner
key = BIP32Key.fromEntropy(seed)
for n in parse_path("m/48'/13'/0'/0'/0'"):
    key = key.ChildKey(n)
owner = PrivateKey(key.WalletImportFormat(), prefix="STM")
print(str(owner))
print(str(owner.pubkey))

returns my new owner key:

5K44k4L7b8VqzbnXk6kR7YqgkoNoK7ANkQ1aWwMQwjfSCwLB9f7
STM56RWQc5F9rZpW8m3epNkJpmcy36FG79dSR8oR6fX87JP6YMZ92

Using beempy keygen

The owner key can also be generated with beempy:

beempy keygen --import-word-list --passphrase --path "m/48'/13'/0'/0'/0'"  

returns

+------------+-------------------------------------------------------+
| Key        | Value                                                 |
+------------+-------------------------------------------------------+
| Public Key | STM56RWQc5F9rZpW8m3epNkJpmcy36FG79dSR8oR6fX87JP6YMZ92 |
+------------+-------------------------------------------------------+
+------------------+-----------------------------------------------------------------------------------+
| Key              | Value                                                                             |
+------------------+-----------------------------------------------------------------------------------+
| Account sequence | 0                                                                                 |
| Key sequence     | 0                                                                                 |
| Key role         | owner                                                                             |
| path             | m/48'/13'/0'/0'/0'                                                                |
| BIP39 wordlist   | museum music dust enlist account arrest evolve inmate rack predict theory section |
| Passphrase       | beem                                                                              |
| Private Key      | 5K44k4L7b8VqzbnXk6kR7YqgkoNoK7ANkQ1aWwMQwjfSCwLB9f7                               |
+------------------+-----------------------------------------------------------------------------------+

It is also possible to use the --network, --role, --account --sequence parameter instead of --path

beempy keygen --import-word-list --passphrase --network 13 --role owner --account 0 --sequence 0

returns the same output.

Using external sources to obtain the same results

We are now trying to use https://iancoleman.io/bip39/ for recovering our owner key.

The seed looks good. We are now selecting BIP32 and entering our path without the key sequence number:

We are toggling "Use hardened address" and selecting the needed key sequence

and receiving

L2gXX3hN5e6KFEXxhENdRuXtUKhDQvCNzYZYw8u6gbJeFKvcFE91

We can now create a WIF from this key:

from beemgraphenebase.account import PrivateKey
print(str(PrivateKey("L2gXX3hN5e6KFEXxhENdRuXtUKhDQvCNzYZYw8u6gbJeFKvcFE91")))

returns

5K44k4L7b8VqzbnXk6kR7YqgkoNoK7ANkQ1aWwMQwjfSCwLB9f7

which is the same key as obtained with beem/beempy.

By using a standard, we are able to derive all keys using different tools.

Changing all four account keys

beempy keygen -p --account-keys --account 0 --export-pub beembot_pub.json

Creates now a new wordlist, which I need to store at a save place and a beembot_pub.json is created which I can use to change my keys:

beempy changekeys --import-pub .\beembot_pub.json beembot

I can use the same wordlist to derive new keys for my next account beempy:

beempy keygen --import-word-list -p --account-keys --account 1 --export-pub beempy-pub.json

Cold storage / hardware wallet

The beempy keygen command should be performed on a separate offline machine. The json-file with the new pubkeys needs to be copied to an online machine for broadcasting.

I could also generate several keys in advance by increasing the account-index and the key-index and storing them all. This reduces the effort in changing a key or creating a new account for a new project. I just need to assign the account-index to account names and remember the actual key-index.

As beempy runs on android under termux, using an old android phone in offline mode seems also be possible. Maybe do a factory reset after creating the keys?

The obtained wordlist and the owner key should be stored offline as well. posting key, active key and memo key can then stored in a password manager for convenience.

Using a hardware wallet which support BIP39 seems to be even a better solution. I will do more research in this direction.


If you like what I do, consider casting a vote for me as witness on Hivesigner or on PeakD

Sort:  

So we use 13' for the next free network slot and for roles

@holger80, how do you know that HIVE network ID is 13?

The next free slot in the SLIP-48 document is 0xd or 13, thus the network id for HIVE on m/48' will be 13' for now (It will only change when a new graphene network registers before HIVE on SLIP-48).

I will rephrase the question. I do understand from this example that it is the next free slot, but the thing is that this document does not list all the existing forks, like GOLOS, CYBER, WHALESHARES etc. that were before the HIVE fork. Has HIVE blockchain registered, acquired or is in the process of getting this number, or it is just a dummy example?

OK, thanks. I got it. The keyword "for now" works for me. I take it as an example that is not yet production ready, since HIVE is not yet on the list.

You have to make a PR to the document.
I created a PR which is was now merged: https://github.com/satoshilabs/slips/pull/925

Slip48
https://github.com/satoshilabs/slips/blob/master/slip-0048.md
has now added HIVE and the network will be now 13:
image.png

Cool! Now it is production ready, because it is in the document.👍🙌 😂🤣

Hi @holger80, you have received a small bonus upvote from MAXUV.
This is to inform you that you now have new MPATH tokens in your Hive-Engine wallet.
Please read this post for more information.
Thanks for being a member of both MAXUV and MPATH!

quite technical for me, but good luck with your project!

Upvoted and resteemed because you are a hero of steem and hive engine and scotbot

Great info at least for me

Great work done

Congratulations @holger80! You have completed the following achievement on the Hive blockchain and have been rewarded with new badge(s) :

Your post got the highest payout of the day

You can view your badges on your board and compare to others on the Ranking
If you no longer want to receive notifications, reply to this comment with the word STOP

Do not miss the last post from @hivebuzz:

Revolution! Revolution!
Vote for us as a witness to get one more badge and upvotes from us with more power!