Blockchain Basics — Part I: Accounts and Keys
Creating Accounts, Managing Private Keys, Using Seed Phrases and HD Paths
Blockchain Basics is a continuing series of short articles, each focusing on a narrow topic.
I began writing here in December 2021. There is a lot of information spread across more than 130 posts, it’s difficult to keep it up-to-date, and many nuggets are presented inline during lessons that are no longer be relevant.
By the end of 2023, I felt strong pressure to move away from Brownie Framework — it had not been updated in nearly a year, and was beginning to show long-term incompatibility issues.
I did a structural rewrite of the degenbot codebase, extracting Brownie-specific elements and replacing them with low-level use of web3.py. With that effort complete, the hard dependency on Brownie is gone. But the soft dependency on Brownie lingers, because archived lessons prior to September 2023 were written against Brownie.
Some of the more complex Brownie lessons are impractical or painful to rewrite, but introductory lessons can and should be kept fresh.
Blockchain Basics will cover important topics using Ape Framework. As these articles are published, I will add links to old lessons so readers can easily find them.
Before working with Ape, I recommend creating a Python virtual environment using PyEnv, and following the installation instructions in my Introduction to Ape Framework.
This post was written using Ape Framework version 0.7.14
What Is An Ethereum Account?
For an Ethereum (or compatible) blockchain, “account” has a narrow meaning, defined by the core developers:
Ethereum has two account types:
Externally-owned account (EOA) – controlled by anyone with the private keys
Contract account – a smart contract deployed to the network, controlled by code
When users think of accounts, they generally think about them indirectly. A typical experience of an account is via “wallet” software that manages private key(s) and allows them to interact with the blockchain through some a built-in interface. Optionally, wallet software may allow connection to a web page and expose the ability to transact with a specific set of contracts through that web page.
An externally-owned account (EOA) is the traditional type used by people. An EOA is identified by a 20 byte public address, traditionally expressed in hexadecimal format with an “0x” prefix.
An EOA can hold a native balance (Ethereum’s native token is called Ether), generate transactions, and deploy smart contracts to be stored on the blockchain.
An EOA is defined by and controlled by a private key, which is 32 bytes. The public address is derived from the private key. A public address only has one private key, and a private key only derives one public address.
A smart contract does not have a private key, but does have a public address. It can hold a balance like an EOA, and generate calls to other contracts, but requires external input to trigger its operation.
What is a Seed Phrase?
A seed phrase is a randomly selected set of words from a pre-generated set. These words are uniformly distributed and used to represent inputs to a hashing algorithm that generates private keys.
Note that I used the plural “keys” above. A given seed phrase may be reused for different blockchains, and can generate an arbitrary number of private keys.
It achieves this through use of a derivation path, which is a set of inputs to the hashing algorithm that select different intermediate steps.
The accepted default Ethereum derivation path is m/44'/60'/0'/0/0
. Using a BIP-44 seed phrase, and this derivation path, most consumer wallet software will generate the same private key.
More sophisticated users can choose custom derivation paths, seed word lists, and word counts.
A nice overview of BIP-44 and derivation paths can be found on Trezor Learn.
How Does Ape Manage Accounts?
Ape will generate an account for you when requested. By default, accounts are stored in the $HOME/.ape/accounts
folder, in JSON-formatted files, and encrypted with an AES-128-CTR cipher.
Generating New Accounts
Make an account using the command ape accounts generate <alias>
as follows:
(ape) btd@dev:~$ ape accounts generate test
Enhance the security of your account by adding additional random input:
[type random characters, then <ENTER>]
Show mnemonic? [Y/n]: Y
Create Passphrase to encrypt account: [type passphrase, then <ENTER>]
Repeat for confirmation: [type passphrase, then <ENTER>]
INFO: Newly generated mnemonic is: input drive boss more myth ritual employ capable bonus wine bean net
SUCCESS: A new account '0x2259AC2d4517Cb0B06b8Aa469BaA85cdb96b6721' with HDPath m/44'/60'/0'/0/0 has been added with the id 'test'
This will generate a new private key, derived from the HDPath shown above, which in turn generates the public address.
Note that the HDPath (m/44'/60'/0'/0/0
) is the Ethereum default listed above. If you want to generate an account from a different path, specify it with the --hdpath
option. If you want to use a different word count, specify that with the --word-count
option.
Importing Brownie Accounts
The format is compatible with the Brownie method, so accounts previously created with Brownie can be imported by copying the JSON files from $HOME/.brownie/accounts
to $HOME/.ape/accounts
.
Importing Other Accounts
Using an Ethereum address generator, we find that private key a990cc5bd36dedec7c7a30467d0ba8ec68372f02d4f573007d9c4f106ad2292b should correspond to address 0xC6e549B5a909D3F8836Ad4BDA3b5dde61B5F1c69.
NOTE: webpages like this are useful for demonstration purposes only. Private keys found here should never be used to handle real assets!
Ape can import the new account via this key:
(ape) btd@dev:~$ ape accounts import test_acct
Enter Private Key: [paste with CTRL+SHIFT+V or by right-clicking in a graphical console]
Create Passphrase to encrypt account:
Repeat for confirmation:
SUCCESS: A new account '0xC6e549B5a909D3F8836Ad4BDA3b5dde61B5F1c69' has been added with the id 'test_acct'
We can confirm here that the public address generated from the private key matches the expected value.
Ape can import an account by inputting the seed phrase. Here we will re-import the same account above, under a different alias:
(ape) btd@dev:~$ ape accounts import test_acct_from_seed --use-mnemonic
Enter mnemonic seed phrase: [copy and paste]
Create Passphrase to encrypt account:
Repeat for confirmation:
SUCCESS: A new account '0x2259AC2d4517Cb0B06b8Aa469BaA85cdb96b6721' has been added with the id 'test_acct_from_seed'
The public address matches the one randomly generated earlier.
Using Accounts on the Ape Console
Within an active Ape console, an account can be unlocked and used via the accounts.load()
method:
(ape) btd@dev:~$ ape console --network ethereum:mainnet
INFO: Connecting to existing Geth node at http://localhost:8545/
In [1]: account = accounts.load('test_acct')
In [2]: account
Out[2]: <KeyfileAccount address=0xC6e549B5a909D3F8836Ad4BDA3b5dde61B5F1c69 alias=test_acct >
In [3]: account.balance
Out[3]: 0
This account could be used to transfer Ether, interact with contracts, or deploy new contracts.