Skip to content

Commit

Permalink
Added experimental memory-hard password scheme for password-protected…
Browse files Browse the repository at this point in the history
… mnemonics to the CLI.
  • Loading branch information
ricmoo committed Jul 20, 2019
1 parent e9558c8 commit 5877418
Show file tree
Hide file tree
Showing 3 changed files with 38 additions and 3 deletions.
1 change: 1 addition & 0 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"@types/node": "10.3.2",
"ethers": ">5.0.0-beta.0",
"mime-types": "2.1.11",
"scrypt-js": "2.0.4",
"solc": "0.5.9",
"solidity-parser-antlr": "^0.3.2"
},
Expand Down
32 changes: 30 additions & 2 deletions packages/cli/src.ts/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import fs from "fs";

import { ethers } from "ethers";
import scrypt from "scrypt-js";

import { getChoice, getPassword, getProgressBar } from "./prompt";

Expand Down Expand Up @@ -383,12 +384,36 @@ async function loadAccount(arg: string, plugin: Plugin, preventFile?: boolean):

// Mnemonic
if (ethers.utils.isValidMnemonic(arg)) {
const mnemonic = arg;
let signerPromise: Promise<ethers.Wallet> = null;
if (plugin.mnemonicPassword) {
signerPromise = getPassword("Password (mnemonic): ").then((password) => {
let node = ethers.utils.HDNode.fromMnemonic(arg, password).derivePath(ethers.utils.defaultPath);
let node = ethers.utils.HDNode.fromMnemonic(mnemonic, password).derivePath(ethers.utils.defaultPath);
return new ethers.Wallet(node.privateKey, plugin.provider);
});

} else if (plugin._xxxMnemonicPasswordHard) {
signerPromise = getPassword("Password (mnemonic; experimental - hard): ").then((password) => {
let passwordBytes = ethers.utils.toUtf8Bytes(password, ethers.utils.UnicodeNormalizationForm.NFKC);
let saltBytes = ethers.utils.arrayify(ethers.utils.HDNode.fromMnemonic(mnemonic).privateKey);

let progressBar = getProgressBar("Decrypting");
return (<Promise<ethers.Wallet>>(new Promise((resolve, reject) => {
scrypt(passwordBytes, saltBytes, (1 << 20), 8, 1, 32, (error, progress, key) => {
if (error) {
reject(error);
} else {
progressBar(progress);
if (key) {
let derivedPassword = ethers.utils.hexlify(key).substring(2);
let node = ethers.utils.HDNode.fromMnemonic(mnemonic, derivedPassword).derivePath(ethers.utils.defaultPath);
resolve(new ethers.Wallet(node.privateKey, plugin.provider));
}
}
});
})));
});

} else {
signerPromise = Promise.resolve(ethers.Wallet.fromMnemonic(arg).connect(plugin.provider));
}
Expand Down Expand Up @@ -454,6 +479,7 @@ export class Plugin {

accounts: Array<WrappedSigner>;
mnemonicPassword: boolean;
_xxxMnemonicPasswordHard: boolean;

gasLimit: ethers.BigNumber;
gasPrice: ethers.BigNumber;
Expand Down Expand Up @@ -523,6 +549,7 @@ export class Plugin {
// Accounts

ethers.utils.defineReadOnly(this, "mnemonicPassword", argParser.consumeFlag("mnemonic-password"));
ethers.utils.defineReadOnly(this, "_xxxMnemonicPasswordHard", argParser.consumeFlag("xxx-mnemonic-password"));

let accounts: Array<WrappedSigner> = [ ];

Expand Down Expand Up @@ -711,6 +738,7 @@ export class CLI {
console.log(" --account-rpc ADDRESS Add the address from a JSON-RPC provider");
console.log(" --account-rpc INDEX Add the index from a JSON-RPC provider");
console.log(" --mnemonic-password Prompt for a password for mnemonics");
console.log(" --xxx-mnemonic-password Prompt for a (experimental) hard password");
console.log("");
console.log("PROVIDER OPTIONS (default: getDefaultProvider)");
console.log(" --alchemy Include Alchemy");
Expand Down Expand Up @@ -754,7 +782,7 @@ export class CLI {
{
let argParser = new ArgParser(args);

[ "debug", "help", "mnemonic-password", "offline", "yes"].forEach((key) => {
[ "debug", "help", "mnemonic-password", "offline", "xxx-mnemonic-password", "yes"].forEach((key) => {
argParser.consumeFlag(key);
});

Expand Down
8 changes: 7 additions & 1 deletion packages/cli/thirdparty.d.ts
Original file line number Diff line number Diff line change
@@ -1 +1,7 @@

declare module "scrypt-js" {
export class ScryptError extends Error {
progress: number;
}
export type ScryptCallback = (error: ScryptError, progress: number, key: Uint8Array) => void;
export default function(password: Uint8Array, salt: Uint8Array, N: number, r: number, p: number, dkLen: number, callback: ScryptCallback): void;
}

0 comments on commit 5877418

Please sign in to comment.