Operating the ENIAC’s main control panel. (public domain)

During some random come-get-free-food party with Jeff at DevCon4, we discussed a technique that would allow Ethereum (as well as most other crypto-currencies) to easily recover the majority of user accounts in the event of a quantum computer attack, migrating to quantum-secure algorithms (such as lamport signatures).

I’ve been promising to get a little article together to explain this, and so here we are. This article will outline a hypothetical migration path that the Ethereum community could follow to rescue the network from quantum computers.

tl;dr: prove you own the account mnemonic phrase, not the private key

What can quantum computers do?

It is important to understand, that while quantum computers can perform a certain class of problems very efficiently, for most problems they provide very little to no advantage.

The two classes of problems we care about are:

Elliptic Curve Cryptography, which uses a one-way operation to create a public key from a private key; knowing the public key does not allow you to compute the private key, but knowing the private key does allow you to compute the public key

operation to create a public key from a private key; knowing the public key you to compute the private key, but knowing the private key you to compute the public key Cryptographic Hash Functions, which create a one-way mapping between a piece of data (called a “pre-image”) to a digest; knowing the digest does not allow you to compute the pre-image, but knowing the pre-image does allows you to compute the digest

Elliptic Curve Cryptography

When an Ethereum account makes a transaction, the account’s private key is used to compute a signature, which is included in the the transaction. A signature implicitly reveals the public key, which is used to verify the transaction.

Quantum computers are excellent at breaking this one-way relationship, allowing them to compute the private key from a public key.

On a post-quantum Earth, this would mean once an account has been used, even once, that anyone could compute the private key, and would gain control over all its funds, assets and permissions.

Keep in mind there is no way to distinguish between two people that each know the private key, regardless of whether they were the person that created it, or they simply acquired it later on. This is what we want to solve.

Cryptographic Hash Functions

Cryptographic hash functions are absolutely amazing. They are used for a huge variety of things in the blockchain universe, and have an almost ridiculous number of possibilities.

More importantly (for the purpose of this article), quantum computers provide nearly no benefit to breaking the one-way relationship. Even a quantum computer cannot compute the pre-image for a given digest (in any practical sense, at least; more below).

The focus of cryptographic hash functions today, is the method in which most wallet software and libraries generate private keys using them.

If you have ever used a crypto-currency wallet, you have likely had to write down something like a “12 word mnemonic backup phrase”. If you have, this technique will work for you, since that mnemonic is basically just hashed many times in a very specific way to generate your private key.

Blinding: Cryptographic Hash Functions for Time Travellers

Another powerful use case for cryptographic hash functions is “blinding”.

Blinding allows us to prove at a future date that we had access to a piece of data in the past, without revealing the actual data until the future date.

For example, imagine you travelled back in time to the summer of 1995, after the airing of “Who Shot Mr. Burns: Part 1” and you wished to eventually prove you knew in advance who attempted the murder of the sun-blotting antagonist, without actually revealing who.

[ SPOILER ALERT: If you are 24-or-so years behind in Simpsons… ]

You would choose some random salt (let’s say "0x614ea47..." ). Then you could compute the cryptographic hash of the pre-image: "maggie shot mr burns 0x614ea47..." , then share the digest with all your friends. They cannot compute the pre-image from the digest, so they cannot know what your message was.

Then after the airing of Part 2, you could reveal the pre-image, allowing them to compute the cryptographic hash of "maggie shot mr burns 0x614ea47..." themselves, for which they would get the same digest you committed to, proving you knew in advance, without having spoiling the surprise.

A quick note about the salt, which is that it is necessary to prevent guessing. Otherwise, your friends could simply try every possible character, checking the cryptographic hash of "smithers shot mr burns" , "bumblebee man shot mr burns" , and so on, until they found a hash that matched.

Putting it all together

This is just an example of a hypothetical outline of the steps required, but let us imagine that tomorrow it is made public that quantum computers exist, and are being used to abscond with all the ethers in the ‘verse.

Ultimately, what we want to achieve is to prove we have access to the private key’s pre-image, which came from the mnemonic backup phrase, which proves that we actually created the account, and did not just tease it out from old transactions on the blockchain.

Using a hard fork, we would freeze the network at some block height, locking up the balances of all externally owned accounts to a known mostly-untampered-with state, and force users to upgrade to a quantum-secure account. Contract addresses do not have an associated public key, so they are already safe.

To Ethereum we would add two new operations:

commitPrivateKeyPreImage(blindedCommit, newAddress)

revealPrivateKeyPreImage(preImage, salt)

Then each user would compute their blinded commit (from their mnemonic backup phrase’s pre-image and a salt), and generate a new quantum-secure account address.

Calling commitPrivateKeyPreImage creates a tentative link from the legacy vulnerable Ethereum account to the new quantum secure account, but without revealing the pre-image of the private key or the salt.

Any attacker may know the private key (since they can use their quantum computer to calculate it from any old transactions the account has sent), but cannot know the pre-image that went into creating the private key in the first place, since it was generated from a cryptographic hash function.

After waiting a safe number of blocks, the user would then call revealPrivateKeyPreImage , where they reveal the pre-image and salt. Ethereum can compute the digest, and locate the tentative link, finalizing it, since the account owner has proved they had access to the original mnemonic, and not merely the private key.

At this point it is too late for an attacker to attempt to submit their own blinded commit with the revealed pre-image and their own salt because there is an older, confirmed commit.

The account is now secure and the user may go on accessing the legacy account’s ether, tokens and interact with any contracts using the new quantum-secure account.

Note: Legacy contracts would require msg.sender and tx.origin preserve the legacy address; also deploying new contracts would need to check the legacy address, in case something that looked like an EOA (because it had no code) was actually an undeployed contract.

Not left as an exercise to the reader…

There is a bit we glossed over that is necessary to handle the BIP-32 derivation from the mnemonic phrase.

The cryptographic hash involved is actually a nested cryptographic hash, using the SHA2–512 cryptographic hash function, wrapped with another algorithm called HMAC . Together this is called HMAC-SHA2–512 , which is resistant to even some of the more advanced quantum computer techniques.

The BIP-32 derivation of a given public key requires the the public key of its parent node, as well as a chain code from its parent. So, we can use these two values together to concisely describe the pre-image and as an implicit salt.

We create two functions; one that generates commit messages and reveal messages, and one that validates commit messages.

// Import the library and functions we need

// - npm install ethers@4.0.26

const ethers = require("ethers");

const arrayify = ethers.utils.arrayify;

const bigNumberify = ethers.utils.bigNumberify;

const concat = ethers.utils.concat;

const hmac = require("ethers/utils/hmac");

const hexlify = ethers.utils.hexlify;

const keccak256 = ethers.utils.keccak256;

const secp256k1 = require("ethers/utils/secp256k1"); /* getReveal(mnemonic)

*

* Returns the necessary data for committing and revealing

* - blindedCommit: share this now

* - parentChainCode + parentPrivateKey: reveal this later

*/

function getReveal(mnemonic) { // Derive the parent node of the the target private key

let node = ethers.utils.HDNode.fromMnemonic(mnemonic);

node = node.derivePath("44'/60'/0'/0"); let blindedCommit = keccak256(concat([

node.chainCode,

node.publicKey

])); return {

// Our blinded commit message

blindedCommit: blindedCommit, // We will need these later to prove our commit message

parentChainCode: node.chainCode,

parentPublicKey: node.publicKey

};

} /* verifyCommit(reveal)

*

* Verifies the commit message and returns the legacy private key

*/

function verifyCommit(reveal) { // Check the pre-commit matches

let check = keccak256(concat([

reveal.parentChainCode,

reveal.parentPublicKey

])); if (check !== reveal.blindedCommit) {

throw new Error("Commit does not match");

} // Data = ser_p(point(k_par)) || ser_32(index); See BIP32

// (...also, the pre-image.)

let data = concat([

reveal.parentPublicKey,

[ 0, 0, 0, 0 ]

]); // Hash the pre-image, with our implicit salt (the chainCode)

let I = hmac.computeHmac("sha512", reveal.parentChainCode, data); // Compute the public key from the intermediate derived key

let IL = bigNumberify(arrayify(I).slice(0, 32));

let ek = new secp256k1.KeyPair(hexlify(IL));

let Ki = ek._addPoint(reveal.parentPublicKey); // Compute the address from the public key

return secp256k1.computeAddress(Ki);

}

Now we can use getReveal to generate a commit message we could send to the blockchain:

let mnemonic = "runway enemy marine pair deposit talk " +

"video battle soda proud giraffe angle"; let wallet = ethers.Wallet.fromMnemonic(mnemonic);

console.log("Initial Account:");

console.log("- private key: " + wallet.privateKey);

console.log("- address: " + wallet.address); let reveal = getReveal(mnemonic) console.log("Commit:");

console.log("- blinded commit: " + reveal.blindedCommit); // *** The output is: *** Initial Account:

- private key: 0xc31f8c6c5a85f63d5302f1fc4386c0f2d8490f...

- address: 0x681f084E853AD9F44947a89Dc6b48Ea2101264d3 Commit:

- blinded commit: 0x20ab056c9f41fa34cb6ba8193d1c441cff427d...

Then in a later block, we can send the parent public key and chain code, which is used to generate the pre-image and as a salt, which the blockchain can then use verifyCommit .

console.log("Reveal:");

console.log("- parent chain code: " + reveal.parentChainCode);

console.log("- parent public key: " + reveal.parentPublicKey); let legacyAddress = verifyCommit(reveal); console.log("Verified:");

console.log("- legacy address: " + legacyAddress); // *** The output is: *** Reveal:

- parent chain code: 0xa39069123e596365a5035f25e12dcd7e3b870...

- parent public key: 0x02c680dcd8a4d7fb18b51482eab0b2f5275be... Verified:

- legacy address: 0x681f084E853AD9F44947a89Dc6b48Ea2101264d3 // Notice that this address matches our initial account above

At this point the blockchain can now safely transfer ownership of the legacy address to the new account.

Conclusion

I expect this will be largely unnecessary, since the upcoming account abstraction will make it much easier to switch accounts in-protocol and I hope to see on-chain contract wallets to become the norm, which have other interesting solutions. Also, quantum computers aren’t likely to be a problem anytime soon. This was more of a thought experiment.

Also, this makes a semi-compelling argument to include SHA2-512 and HMAC-SHA2–512 opcodes (or precompiles) in the EVM, which would allow for interesting BIP-032 integration on-chain, such as computing addresses from extended public keys.

Anyways, any and all feedback is welcome, and if you’d like to keep up-to-date with my random projects and ramblings, follow me on twitter or GitHub.

*smile emoji*