Skip to content

Commit

Permalink
Encrypt deployer PK on .env file (when using hardhat) (#1008)
Browse files Browse the repository at this point in the history
  • Loading branch information
carletex authored Dec 11, 2024
1 parent 9c4d3a6 commit 8c65441
Show file tree
Hide file tree
Showing 10 changed files with 253 additions and 25 deletions.
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,15 @@
},
"scripts": {
"account": "yarn workspace @se-2/hardhat account",
"account:import": "yarn workspace @se-2/hardhat account:import",
"account:generate": "yarn workspace @se-2/hardhat account:generate",
"chain": "yarn workspace @se-2/hardhat chain",
"fork": "yarn workspace @se-2/hardhat fork",
"deploy": "yarn workspace @se-2/hardhat deploy",
"verify": "yarn workspace @se-2/hardhat verify",
"hardhat-verify": "yarn workspace @se-2/hardhat hardhat-verify",
"compile": "yarn workspace @se-2/hardhat compile",
"generate": "yarn workspace @se-2/hardhat generate",
"generate": "yarn account:generate",
"flatten": "yarn workspace @se-2/hardhat flatten",
"hardhat:lint": "yarn workspace @se-2/hardhat lint",
"hardhat:lint-staged": "yarn workspace @se-2/hardhat lint-staged",
Expand Down
4 changes: 3 additions & 1 deletion packages/hardhat/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,7 @@

# To access the values stored in this .env file you can use: process.env.VARIABLENAME
ALCHEMY_API_KEY=
DEPLOYER_PRIVATE_KEY=
ETHERSCAN_MAINNET_API_KEY=

# Don't fill this value manually, run yarn generate to generate a new account or yarn account:import to import an existing PK.
DEPLOYER_PRIVATE_KEY_ENCRYPTED=
4 changes: 2 additions & 2 deletions packages/hardhat/deploy/00_deploy_your_contract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ const deployYourContract: DeployFunction = async function (hre: HardhatRuntimeEn
When deploying to live networks (e.g `yarn deploy --network sepolia`), the deployer account
should have sufficient balance to pay for the gas fees for contract creation.
You can generate a random account with `yarn generate` which will fill DEPLOYER_PRIVATE_KEY
with a random private key in the .env file (then used on hardhat.config.ts)
You can generate a random account with `yarn generate` or `yarn account:import` to import your
existing PK which will fill DEPLOYER_PRIVATE_KEY_ENCRYPTED in the .env file (then used on hardhat.config.ts)
You can run the `yarn account` command to check your balance in every network.
*/
const { deployer } = await hre.getNamedAccounts();
Expand Down
3 changes: 2 additions & 1 deletion packages/hardhat/hardhat.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@ import { task } from "hardhat/config";
import generateTsAbis from "./scripts/generateTsAbis";

// If not set, it uses the hardhat account 0 private key.
// You can generate a random account with `yarn generate` or `yarn account:import` to import your existing PK
const deployerPrivateKey =
process.env.DEPLOYER_PRIVATE_KEY ?? "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80";
process.env.__RUNTIME_DEPLOYER_PRIVATE_KEY ?? "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80";
// If not set, it uses our block explorers default API keys.
const etherscanApiKey = process.env.ETHERSCAN_MAINNET_API_KEY || "DNXJA8RX2Q3VZ4URQIWP7Z68CJXQZSC6AW";
const etherscanOptimisticApiKey = process.env.ETHERSCAN_OPTIMISTIC_API_KEY || "RM62RDISS1RH448ZY379NX625ASG1N633R";
Expand Down
7 changes: 5 additions & 2 deletions packages/hardhat/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@
"version": "0.0.1",
"scripts": {
"account": "hardhat run scripts/listAccount.ts",
"account:import": "hardhat run scripts/importAccount.ts",
"account:generate": "hardhat run scripts/generateAccount.ts",
"chain": "hardhat node --network hardhat --no-deploy",
"check-types": "tsc --noEmit --incremental",
"compile": "hardhat compile",
"deploy": "hardhat deploy",
"deploy": "ts-node scripts/runHardhatDeployWithPK.ts",
"fork": "MAINNET_FORKING_ENABLED=true hardhat node --network hardhat --no-deploy",
"generate": "hardhat run scripts/generateAccount.ts",
"generate": "yarn account:generate",
"flatten": "hardhat flatten",
"lint": "eslint --config ./.eslintrc.json --ignore-path ./.eslintignore ./*.ts ./deploy/**/*.ts ./scripts/**/*.ts ./test/**/*.ts",
"lint-staged": "eslint --config ./.eslintrc.json --ignore-path ./.eslintignore",
Expand Down Expand Up @@ -49,6 +51,7 @@
"typescript": "<5.6.0"
},
"dependencies": {
"@inquirer/password": "^4.0.2",
"@openzeppelin/contracts": "^5.0.2",
"@typechain/ethers-v6": "^0.5.1",
"dotenv": "^16.4.5",
Expand Down
37 changes: 25 additions & 12 deletions packages/hardhat/scripts/generateAccount.ts
Original file line number Diff line number Diff line change
@@ -1,42 +1,55 @@
import { ethers } from "ethers";
import { parse, stringify } from "envfile";
import * as fs from "fs";
import password from "@inquirer/password";

const envFilePath = "./.env";

/**
* Generate a new random private key and write it to the .env file
*/
const setNewEnvConfig = (existingEnvConfig = {}) => {
console.log("👛 Generating new Wallet");
const getValidatedPassword = async () => {
while (true) {
const pass = await password({ message: "Enter a password to encrypt your private key:" });
const confirmation = await password({ message: "Confirm password:" });

if (pass === confirmation) {
return pass;
}
console.log("❌ Passwords don't match. Please try again.");
}
};

const setNewEnvConfig = async (existingEnvConfig = {}) => {
console.log("👛 Generating new Wallet\n");
const randomWallet = ethers.Wallet.createRandom();

const pass = await getValidatedPassword();
const encryptedJson = await randomWallet.encrypt(pass);

const newEnvConfig = {
...existingEnvConfig,
DEPLOYER_PRIVATE_KEY: randomWallet.privateKey,
DEPLOYER_PRIVATE_KEY_ENCRYPTED: encryptedJson,
};

// Store in .env
fs.writeFileSync(envFilePath, stringify(newEnvConfig));
console.log("📄 Private Key saved to packages/hardhat/.env file");
console.log("🪄 Generated wallet address:", randomWallet.address);
console.log("\n📄 Encrypted Private Key saved to packages/hardhat/.env file");
console.log("🪄 Generated wallet address:", randomWallet.address, "\n");
console.log("⚠️ Make sure to remember your password! You'll need it to decrypt the private key.");
};

async function main() {
if (!fs.existsSync(envFilePath)) {
// No .env file yet.
setNewEnvConfig();
await setNewEnvConfig();
return;
}

// .env file exists
const existingEnvConfig = parse(fs.readFileSync(envFilePath).toString());
if (existingEnvConfig.DEPLOYER_PRIVATE_KEY) {
if (existingEnvConfig.DEPLOYER_PRIVATE_KEY_ENCRYPTED) {
console.log("⚠️ You already have a deployer account. Check the packages/hardhat/.env file");
return;
}

setNewEnvConfig(existingEnvConfig);
await setNewEnvConfig(existingEnvConfig);
}

main().catch(error => {
Expand Down
72 changes: 72 additions & 0 deletions packages/hardhat/scripts/importAccount.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { ethers } from "ethers";
import { parse, stringify } from "envfile";
import * as fs from "fs";
import password from "@inquirer/password";

const envFilePath = "./.env";

const getValidatedPassword = async () => {
while (true) {
const pass = await password({ message: "Enter a password to encrypt your private key:" });
const confirmation = await password({ message: "Confirm password:" });

if (pass === confirmation) {
return pass;
}
console.log("❌ Passwords don't match. Please try again.");
}
};

const getWalletFromPrivateKey = async () => {
while (true) {
const privateKey = await password({ message: "Paste your private key:" });
try {
const wallet = new ethers.Wallet(privateKey);
return wallet;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
} catch (e) {
console.log("❌ Invalid private key format. Please try again.");
}
}
};

const setNewEnvConfig = async (existingEnvConfig = {}) => {
console.log("👛 Importing Wallet\n");

const wallet = await getWalletFromPrivateKey();

const pass = await getValidatedPassword();
const encryptedJson = await wallet.encrypt(pass);

const newEnvConfig = {
...existingEnvConfig,
DEPLOYER_PRIVATE_KEY_ENCRYPTED: encryptedJson,
};

// Store in .env
fs.writeFileSync(envFilePath, stringify(newEnvConfig));
console.log("\n📄 Encrypted Private Key saved to packages/hardhat/.env file");
console.log("🪄 Imported wallet address:", wallet.address, "\n");
console.log("⚠️ Make sure to remember your password! You'll need it to decrypt the private key.");
};

async function main() {
if (!fs.existsSync(envFilePath)) {
// No .env file yet.
await setNewEnvConfig();
return;
}

const existingEnvConfig = parse(fs.readFileSync(envFilePath).toString());
if (existingEnvConfig.DEPLOYER_PRIVATE_KEY_ENCRYPTED) {
console.log("⚠️ You already have a deployer account. Check the packages/hardhat/.env file");
return;
}

await setNewEnvConfig(existingEnvConfig);
}

main().catch(error => {
console.error(error);
process.exitCode = 1;
});
19 changes: 14 additions & 5 deletions packages/hardhat/scripts/listAccount.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,26 @@ dotenv.config();
import { ethers, Wallet } from "ethers";
import QRCode from "qrcode";
import { config } from "hardhat";
import password from "@inquirer/password";

async function main() {
const privateKey = process.env.DEPLOYER_PRIVATE_KEY;
const encryptedKey = process.env.DEPLOYER_PRIVATE_KEY_ENCRYPTED;

if (!privateKey) {
console.log("🚫️ You don't have a deployer account. Run `yarn generate` first");
if (!encryptedKey) {
console.log("🚫️ You don't have a deployer account. Run `yarn generate` or `yarn account:import` first");
return;
}

const pass = await password({ message: "Enter your password to decrypt the private key:" });
let wallet: Wallet;
try {
wallet = (await Wallet.fromEncryptedJson(encryptedKey, pass)) as Wallet;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
} catch (e) {
console.log("❌ Failed to decrypt private key. Wrong password?");
return;
}

// Get account from private key.
const wallet = new Wallet(privateKey);
const address = wallet.address;
console.log(await QRCode.toString(address, { type: "terminal", small: true }));
console.log("Public address:", address, "\n");
Expand Down
58 changes: 58 additions & 0 deletions packages/hardhat/scripts/runHardhatDeployWithPK.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import * as dotenv from "dotenv";
dotenv.config();
import { Wallet } from "ethers";
import password from "@inquirer/password";
import { spawn } from "child_process";
import { config } from "hardhat";

/**
* Unencrypts the private key and runs the hardhat deploy command
*/
async function main() {
const networkIndex = process.argv.indexOf("--network");
const networkName = networkIndex !== -1 ? process.argv[networkIndex + 1] : config.defaultNetwork;

if (networkName === "localhost" || networkName === "hardhat") {
// Deploy command on the localhost network
const hardhat = spawn("hardhat", ["deploy", ...process.argv.slice(2)], {
stdio: "inherit",
env: process.env,
shell: process.platform === "win32",
});

hardhat.on("exit", code => {
process.exit(code || 0);
});
return;
}

const encryptedKey = process.env.DEPLOYER_PRIVATE_KEY_ENCRYPTED;

if (!encryptedKey) {
console.log("🚫️ You don't have a deployer account. Run `yarn generate` or `yarn account:import` first");
return;
}

const pass = await password({ message: "Enter password to decrypt private key:" });

try {
const wallet = await Wallet.fromEncryptedJson(encryptedKey, pass);
process.env.__RUNTIME_DEPLOYER_PRIVATE_KEY = wallet.privateKey;

const hardhat = spawn("hardhat", ["deploy", ...process.argv.slice(2)], {
stdio: "inherit",
env: process.env,
shell: process.platform === "win32",
});

hardhat.on("exit", code => {
process.exit(code || 0);
});
// eslint-disable-next-line @typescript-eslint/no-unused-vars
} catch (e) {
console.error("Failed to decrypt private key. Wrong password?");
process.exit(1);
}
}

main().catch(console.error);
Loading

0 comments on commit 8c65441

Please sign in to comment.