Don't tell gavofyork or tomusdrw, but two days ago I robbed myself. Let me explain.
Since mid 2015 I have been aware of Ethereum. I read up, felt that it was the future, and decided to invest some of my personal savings in the project (by proxy of their Ether token).
Fast forward a year or so and rather than simply keeping 'in the know' as regards developments in the project I decided to dive deep and try and get to grips with some of the complexities of the project. Why? Simple. Double Negative (the company which I own and operate) has less on its plate, and we are considering building out web based applications that integrate with the Ethereum blockchain.
Two days ago I stormed into the Parity Gitter shouting and screaming. I was being robbed !
Having read this thread on reddit the previous day, security was at the top of my list of 'things to investigate'. Talk about synchronicity.. All of a sudden I was being asked to sign a request for a transaction which I hadn't initiated through the Parity Wallet UI.
Lets pause for a moment, and explain/outline in depth the totality of my post-resolution research. Then I will explain what had happened. Here we go..
What I learned..
NOTE I tried to make this as beginner accessible as possible, but the engineer in me got a bit out of hand. Apologies if it gets too technical.
What is an Ethereum wallet
In the physical world you interface with a physical wallet. You put fiat cash into you wallet, and take it out when you want to spend some.
In cryptocurrency land a wallet tends to refer to the platform through which you interface with your Ether.
In that vein, the following are Ether wallets:
- MyEtherWallet - a web based wallet written in Javascript.
- Ethereum Wallet - the wallet software created by the Ethereum foundation.
- Parity Wallet - a web based wallet interface coupled with the Parity Ethereum client.
- MetaMask.io - a wallet served as a chrom extension. Watch this video outlining how it works.
Behind the scenes
Behind the scenes each of these wallets is working with a key file.
- In Parity (on macOS) these are stored in
~/.parity/keys
. - Ethereum Wallet interfaces with your running Geth node which (on macOS) stores keys in
/Library/Ethereum/keystore
.
What is in a key file?
The below refers specifically to Ethereum Wallet and Parity Wallet.
As outlined in this Stack Exchange question this key file contains your private key encrypted using a (user defined) password.
From your private key is derived a public key and an address. How this is done is outlined in this fantastic answer.
Your address is the address that others use to send you Ether. The address is also contained within the key file.
Are addresses unique?
I was intrigued as to how one guarantees the uniqueness of Ethereum addresses. There is an answer for that too.
A wallet generates a random private key. There is a "2^160 or about 1 in 1,461,501,637,330,902,918,203,684,832,716,283,019,655,932,542,976" of two private keys colliding.
This comment on reddit explains this extremely well.
So basically whilst private keys can theoretically collide, in practice they don't because of PROBABILITY !! :)
Offline wallets
MyEtherWallet is wallet software written in Javascript. You can download the source code and run it on an 'air locked' device to generate a wallet offline. This refers to a device that is not connected to the Internet. This is outlined by the author here.
But how can you create a wallet offline?!
Another confusing point for me was how you can create an account without being on the Internet.
My initial naive thinking was: "If this new account is not transferred to the blockchain over the Internet, how will it be 'registered'".
Based on the above explanation of public/private key combinations (and their relation to account addresses) hopefully you can see the answer. You have created a random private key from which the account is derived. That same private key will never (probabilistically) be generated again.
As outlined in the Ethereum docs, "The Ethereum blockchain tracks the state of every account, and all state transitions on the Ethereum blockchain are transfers of value and information between accounts.".
That is to say, accounts don't need to be 'created' or 'registered' on the blockchain - all possible accounts inherently exist.
This comment provides clarity as regards this matter.
Any other tools?
There is another tool called Icebox which will allow you to generate account(s) on cold storage.
This tool creates unique Hierarchical Deterministic accounts. The best explanation of this can be found here.
Hierarchical Deterministic accounts are a number of accounts that are cryptographically related and will be deterministically created given a certain mnemonic input.
The "12 word mnemonic" is basically a passphrase. It is generated using complex cryptography outlined in BIP 39. With Icebox some additional randomness is supplied in the form of user supplied entropy.
Tell me more about BIP 39
I found some interesting stuff about BIP 39. Here you go.
-
This isn't the implementation used by Icebox, but I enjoyed reading the code. An implementation of BIP 39 in Javascript.
-
Here is an explanation of the pros/cons/limitations of BIP39.
Keystore formats
In principle wallet software can produce keystore files in any format that it likes, as long as it retains knowledge of how to decode that contained within into the private key.
The defined format (that is utilised by Geth and Parity) is detailed here. 'Secret Storage Definition' - how mysterious.
If one were implementing their own wallet software it would be perverse not to follow this format as it allows simple cross compatibility.
Transferring Ether
In the world of Ethereum, 'Gas' is an important concept. Here is an explanation of gas.
Transferring Ether is an example of a transaction. To execute such a transaction you need to 'sign' it. This essentially amounts to authenticating that you want the transaction to proceed.
Etherscan has a web form which allows you to post the hex code for an already signed transaction directly to the network.
This answer outlines in laymans terms that:
"Given the transaction, its signature and the signer's public key, any party can check for the transaction's authenticity. Authenticity means that only the signer could have signed the respective transaction."
Note that your private key is not required.
Essentially complex cryptography allows validation of a transactions source without knowledge of the private key that initially signed it.
This is outlined in this comment:
"This is one of the most magical things about bitcoin/crypto currency IMHO. Private/Public keypairs are "mathematical trap doors", that is, the private key, run through the crypto algorithm, always solves to the same public key. The blockchain will never see the private key, EVER! When you sign a transaction you do not show the private key, but the blockchain can cryptographically ensure that the private key had to be used to sign the transaction for that public address. This means that public/private keypairs can be created without interacting with the blockchain.".
This question demystifies the process of transaction signing.
But how does one get a signed transaction hex?
Um.. you can use Icebox on an airlocked device.
You can also use MyEtherWallet's offline transaction functionality, but I do not feel that its usage is as intuative.
One important consideration with transactions is the nonce. Nonces need to be incremented with each transaction. On an air gapped device you can't access the blockchain to discern the account nonce so must identify it manually.
Here is a post on nonces to prevent double spends.
This answer outlines what happens if you use the wrong nonce.
Demonstration
I wanted to be sure that my understanding of public/private keys, account numbers, and key stores was correct. Furthermore if you generate an offline wallet you want to be sure that the passcode or mnemonic that you have used will always generate the correct account hex. As such I did some testing. You can do the same to reassure yourself..
- Download Icebox.
Note: This is my own fork. Only the hdPathString
has been changed as seen in this singular commit. This change was made so it matches these proposed standards that are used by Jaxx, MyEtherWallet etc.
-
Generate some accounts (follow the README), and note your mnemonic.
-
Note your account hex(s) somewhere.
-
Visit MyEtherWallet.com or download their source code from their github and run it on your local machine.
-
Navigate to the 'View Wallet Info' tab, select the 'Mnemonic phrase' option, and input your phrase.
At this point a popup should appear which displays the same account hexes as generated by Icebox.
Note There may be differences in capitalisation as Icebox does not implement Checksums.
MyEtherWallet does not generate a key store file for usage with Mist or Parity (I am not sure why..) and as such I wanted to take this further such that were one to want to they could import a wallet from Icebox to Geth/Parity.
So.. next..
-
Setup a Parity node and run it (
parity --warp
). -
As outlined in 'Getting Started', install
web3
withnpm
. -
Whilst you are at it, install
keythereum
withnode
too. Their github is here. -
Start a
node
console. -
Type/have a look through the following commented code snippet.
//Include and initialise web3
var Web3 = require("web3")
var web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"));
//Output the current block number (to check things are working)
web3.eth.blockNumber
//Output a list of accounts. On a new Parity node this will be empty []
web3.eth.accounts
//Include the keytherum library
var keythereum = require("keythereum");
//Define some options/config
var password = "wheethereum";
var kdf = "pbkdf2";
var options = {
kdf: "pbkdf2",
cipher: "aes-128-ctr",
kdfparams: {
c: 262144,
dklen: 32,
prf: "hmac-sha256"
}
};
//The unencrypted private key that MyEtherWallet displayed to us
var privateKey="0065c0e3b3da40d985e8467e5a9e2cfaa1627b55ca7d1ec2cd13a3c0a25d784f";
//Require complex cryptography library
var ecdsa = new (require("elliptic").ec)("secp256k1");
//Convert the private key to its public key counterpart
var publicKey = new Buffer(ecdsa.keyFromPrivate(privateKey).getPublic("arr"));
//Require the utility library for converting a public key to an address hex
var pubToAddress = require("ethereumjs-util").pubToAddress;
//Use it
var address = pubToAddress(publicKey).toString("hex");
//At this point you can print out the address, and verify that it matches what you started with in Icebox.
//Next I want to produce a keystore file for Mist/Parity
//I wasn't sure how to make a salt and a Initialisation Vector.
//My understanding is that they are random and play a part in the crypto security of this all.
//I use keythereums create method and steal the salt/IV from there.
//Can someone more knowledgable confirm that this is OK? :P
var dk = keythereum.create();
//I build the keystore file contents
var key = keythereum.dump(password, privateKey, dk.salt, dk.iv);
//And convert it to JSON
var jsonKey = JSON.stringify(key);
At this point I can copy the contents of jsonKey
and place it in a file in the ~/.parity/keys
directory.
If I now restart my Parity node (so it reloads the keys) and execute web3.eth.accounts
from my node
console one should see the account hex that they generated many moons ago using Icebox.
You can also import the keystore file into MyEtherWallet, enter your password, and verify that it works there too.
So what happened?
I wasn't actually being robbed. I had previously created a script which attempts to register a contract on the blockchain.
I hadn't appreciated that I had opened this page in my browser. I had also forgotten that it used web3
to communicate with the blockchain in such a manner.
The transaction I was being asked to sign was not for all my funds (as I had initially thought in my panicked state). I didn't realise this until many hours later.
When I saw the signing request I panicked, took a screenshot, and instantly rushed to the Parity Gitter.
Gav, Tomasz, and user @ppratscher helped reassure me that I was probably secure and ran through with me what could have possibly gone wrong. I learned a lot (so it wasn't all for nothing), and am endlessly appreciative that they took the time to help out.
After many hours of security research I was closing some browser tabs and noticed some weird logs in my console. This was my Eureka moment. I investigated what had caused these messages to be outputted. I had written code (that I had forgotten about) such that failure to submit the contract resulted in the console logs. Only at this point did I think to look at the screenshot that I had taken. Looking at it with a clearer mind made it patently apparent that the transaction was for 0 ETH.
Oops.
How do I actually stay secure?
Hopefully the content of this post makes it apparent that the valuable piece of information in this puzzle is your private key.
If someone has your private key in its raw form then they can sign transactions from your account.
As such to keep yourself secure you need to make sure that no-one can access your unencrypted private key.
In principle you can hand out your encrypted private key to whoever you want and they can do absolutely nothing. If however they manage to discern your passcode or mnemonic (with which the key is encrypted), then you are at risk.
The reason that people advocate offline wallets is because no-one, and no malware can access a locked down private key. Furthermore, if you sign a transaction on an offline machine you can submit it to the network without having to type your private key into a live machine. This means that even if someone had managed to install a keylogger on your machine they'd never be able to get your private key.
Respect the sanctity of your private key.
TLDR
Some idiocy, and some panic made me think I was being robbed.
I learned a lot more about Ethereum which will hopefully help Double Negative Solutions to produce some awesome products (based on the platform) in the future.
Hopefully you can learn something from my foolishness too.
Questions/comments
If you have any questions or anything needs clarifying, please let me know.
If anything is wrong, or you feel you could improve my knowledge/understanding in any way I would really appreciate your insight.