hardhat icon indicating copy to clipboard operation
hardhat copied to clipboard

Encrypt Key Management with hardhat, add alt to the practice of placing private keys into `.env` or environment variables

Open PatrickAlphaC opened this issue 3 years ago • 7 comments

As other users have mentioned, like here and here, the current recommended way to store keys isn't ideal. Right now, the convention is to put the private key as an environment variable, which many new engineers have mistakenly pushed up to github.

Other frameworks like brownie and dapptools have built in encryption for the keys, where you input your key into the command line, and it stores the key in a [keystore] (https://kb.myetherwallet.com/en/security-and-privacy/what-is-a-keystore-file/) json file, which means you can only access it with a password. This solves two issues:

  1. It removes accidentally putting your key in plaintext into a file in your project
  2. It encrypts your key so even if you do accidentally do something with it... it's encrypted.

I think the API for this would look some like as follows:

npx hardhat accounts import

And you'd be prompted for your private key, account name, and a password.

Then, when you do npx hardhat accounts list it adds the name of the account.

Then, if your hardhat.config.js, you can just pick the name of your account, or maybe the address, or something.

PatrickAlphaC avatar Jan 16 '22 15:01 PatrickAlphaC

Thanks for the suggestions you've done lately, @PatrickAlphaC! Both of them are things that we definitely need to implement.

alcuadrado avatar Jan 19 '22 14:01 alcuadrado

Working across 4 different deployment frameworks gives me context of which ones have features I value :)

PatrickAlphaC avatar Jan 20 '22 23:01 PatrickAlphaC

This issue was marked as stale because it didn't have any activity in the last 30 days. If you think it's still relevant, please leave a comment indicating so. Otherwise, it will be closed in 7 days.

github-actions[bot] avatar May 26 '22 12:05 github-actions[bot]

This issue was closed because it has been stalled for 7 days with no activity.

github-actions[bot] avatar Jun 02 '22 12:06 github-actions[bot]

I was just thinking of creating a plugin that does that and find this issue. I wonder if you guys are doing something for it or it I should go for the plugin I have in mind...

sullof avatar Jun 10 '22 03:06 sullof

Today I risked to push a change in the .env file and I wrote a simple wrapper that manages encrypted private keys. It works, but there are some issues with the output of the commands from npx hardat execution. If someone wants to take a look: https://github.com/secrez/hardhood Later, I wrote a more general solution, that manages env files https://github.com/secrez/cryptoenv

sullof avatar Jun 13 '22 05:06 sullof

I've tried to give a own solution to this case but the deploy method failed.

I've generated the keystore JSON files basically following these steps:

// instance a HDWallet from a mneumonic
const wallet = ethers.utils.HDNode.fromMnemonic(mneumonic);
// instance a Wallet from the path the first address
const wallet = hdWallet.derivePath(`m/44'/60'/0'/0/0`);
// encrypt the wallet with some "somepassword"
const keyStore = await encryptKeystore(wallet, "somepassword");
// serialize the encrypted keystore to a file
fs.writeFileSync("keystore.json", keyStore);

The full code can be found in this repository: https://github.com/fabianorodrigo/wallet-json-keystore-generator

With the keystore files created, I would use them to deploy my contracts, but I receive an error Cannot read properties of null (reading 'sendTransaction') when my Factory.deploy() is called.

export async function getSigners(password: string): Promise<Wallet[]> {
  //read ADMIN the keystore file
  const admin = await openKeyStoreFile(
    "./scripts/keyStores/keystore_0x97b6183621504b18Ccb97D0422c33a5D3601b862.json",
    password
  );
  //read MANAGER the keystore file
  const manager = await openKeyStoreFile(
    "./scripts/keyStores/keystore_0x958a7E51e39725e866440a223566229841DCFC20.json",
    password
  );

  //read TEAMMEMBER the keystore file
  const teamMember = await openKeyStoreFile(
    "./scripts/keyStores/keystore_0xADA62933dEd1F05764a28DD9DFEB2E8CFAe18731.json",
    password
  );
  return [admin, manager, teamMember];
}
const password = readlineSync.question("Inform the KeyStore JSON files password: ",
  {
    hideEchoBack: true,
  }
);

const [admin, manager, teamMember] = await getSigners(password);
const ETHPoolFactory = await hardhatEthers.getContractFactory("ETHPool");
const ethPool = await ETHPoolFactory.deploy(manager.address);

The fullcode can be found in scripts/deploy.ts from another repository: https://github.com/fabianorodrigo/ETHPool

Is there anything I'm missing?

fabianorodrigo avatar Aug 01 '22 20:08 fabianorodrigo

Hey, we needed something like this so we ended up building it into a hardhat plugin: https://github.com/edgeandnode/hardhat-secure-accounts. It's still early stages and will keep improving it but we've been using it for a bit so thought about sharing here.

The plugin uses ethers wallet encryption to store menmonics using keystore files in your project folder; when unlocking you get a prompt that lets you choose which account and password to use. We tried to build it to be flexible for most use cases, so you can unlock and get a wallet, a signer or a provider. It adds a couple tasks and extends the hardhat environment, I suggest taking a look at the README if you are interested in.

Here is a quick sample of how you could use it, first import a mnemonic into the project:

npx hardhat accounts new

Then use it on your scripts/tasks/etc:

import hre from 'hardhat'

const wallet = await hre.accounts.getWallet()
const signerWithAddress = await hre.accounts.getSigner()
const provider = await hre.accounts.getProvider()

tmigone avatar Aug 31 '22 08:08 tmigone

With 1password8 you can retrieve password using CLI, and I use "@1password/op-js" package to retrieve private key stored in 1password. sample typescript code below will retrieve private key stored under item "Metamask" as type "password" with label "Deployer".

import { item } from "@1password/op-js";

const DEPLOYER: string =
  item
    .get("Metamask")
    .fields?.filter((i) => i.type === `CONCEALED` && i.label === `Deployer`)
    ?.map((e) => e.value)
    ?.toString() || "";

SJ50 avatar Oct 23 '22 00:10 SJ50

Chainlink just published a package which should solve this issue.

https://www.npmjs.com/package/@chainlink/env-enc https://youtu.be/uMX7pLKf3YE

KuphJr avatar Apr 07 '23 00:04 KuphJr

Oooo. Perhaps a PR to the docs @fvictorio?

PatrickAlphaC avatar Apr 07 '23 00:04 PatrickAlphaC

Nice! Working on this is high in our roadmap, so we'll definitely take a look at that package, thanks for letting us know!

fvictorio avatar Apr 13 '23 08:04 fvictorio

We just released Hardhat v2.19.0, which includes a new feature, configuration variables, that should help with this. You can read about it here.

I'm closing this for now. There are some improvements we'll likely make to this feature, but we should open new issues for them.

fvictorio avatar Nov 02 '23 16:11 fvictorio