Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #301 from LiskHQ/267_move_core_specific_commands
Move core specific commands - Closes #267
- Loading branch information
Showing
11 changed files
with
869 additions
and
76 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
/* | ||
* Copyright © 2020 Lisk Foundation | ||
* | ||
* See the LICENSE file at the top-level directory of this distribution | ||
* for licensing information. | ||
* | ||
* Unless otherwise agreed in a custom licensing agreement with the Lisk Foundation, | ||
* no part of this software, including this file, may be copied, modified, | ||
* propagated, or distributed except according to the terms contained in the | ||
* LICENSE file. | ||
* | ||
* Removal or modification of this copyright notice is prohibited. | ||
* | ||
*/ | ||
|
||
import { cryptography } from 'lisk-sdk'; | ||
import { isValidInteger } from '@liskhq/lisk-validator'; | ||
import Command, { flags as flagParser } from '@oclif/command'; | ||
import * as fs from 'fs-extra'; | ||
import * as path from 'path'; | ||
|
||
export default class HashOnionCommand extends Command { | ||
static description = ` | ||
Creates hash onion output to be used by forger. | ||
`; | ||
|
||
static examples = ['hash-onion --count=1000000 --distance=2000']; | ||
|
||
static flags = { | ||
output: flagParser.string({ | ||
char: 'o', | ||
description: 'Output file path', | ||
}), | ||
count: flagParser.integer({ | ||
char: 'c', | ||
description: 'Total number of hashes to produce', | ||
default: 1000000, | ||
}), | ||
distance: flagParser.integer({ | ||
char: 'd', | ||
description: 'Distance between each hashes', | ||
default: 1000, | ||
}), | ||
}; | ||
|
||
// eslint-disable-next-line @typescript-eslint/require-await | ||
async run(): Promise<void> { | ||
const { | ||
flags: { output, count, distance }, | ||
} = this.parse(HashOnionCommand); | ||
|
||
if (distance <= 0 || !isValidInteger(distance)) { | ||
throw new Error('Invalid distance. Distance has to be positive integer'); | ||
} | ||
|
||
if (count <= 0 || !isValidInteger(count)) { | ||
throw new Error('Invalid count. Count has to be positive integer'); | ||
} | ||
|
||
if (output) { | ||
const { dir } = path.parse(output); | ||
fs.ensureDirSync(dir); | ||
} | ||
|
||
const seed = cryptography.generateHashOnionSeed(); | ||
|
||
const hashBuffers = cryptography.hashOnion(seed, count, distance); | ||
const hashes = hashBuffers.map(buf => buf.toString('base64')); | ||
|
||
const result = { count, distance, hashes }; | ||
|
||
if (output) { | ||
fs.writeJSONSync(output, result); | ||
} else { | ||
this.printJSON(result); | ||
} | ||
} | ||
|
||
public printJSON(message?: object, pretty = false): void { | ||
if (pretty) { | ||
this.log(JSON.stringify(message, undefined, ' ')); | ||
} else { | ||
this.log(JSON.stringify(message)); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
/* | ||
* Copyright © 2020 Lisk Foundation | ||
* | ||
* See the LICENSE file at the top-level directory of this distribution | ||
* for licensing information. | ||
* | ||
* Unless otherwise agreed in a custom licensing agreement with the Lisk Foundation, | ||
* no part of this software, including this file, may be copied, modified, | ||
* propagated, or distributed except according to the terms contained in the | ||
* LICENSE file. | ||
* | ||
* Removal or modification of this copyright notice is prohibited. | ||
* | ||
*/ | ||
import { cryptography } from 'lisk-sdk'; | ||
import Command, { flags as flagParser } from '@oclif/command'; | ||
|
||
import { flags as commonFlags } from '../../utils/flags'; | ||
import { getPassphraseFromPrompt } from '../../utils/reader'; | ||
|
||
interface Args { | ||
readonly encryptedPassphrase?: string; | ||
} | ||
|
||
const processInputs = (password: string, encryptedPassphrase: string) => { | ||
const encryptedPassphraseObject = cryptography.parseEncryptedPassphrase(encryptedPassphrase); | ||
const passphrase = cryptography.decryptPassphraseWithPassword(encryptedPassphraseObject, password); | ||
|
||
return { passphrase }; | ||
}; | ||
|
||
export default class DecryptCommand extends Command { | ||
static args = [ | ||
{ | ||
name: 'encryptedPassphrase', | ||
description: 'Encrypted passphrase to decrypt.', | ||
required: true, | ||
}, | ||
]; | ||
|
||
static description = ` | ||
Decrypts your secret passphrase using the password which was provided at the time of encryption. | ||
`; | ||
|
||
static examples = [ | ||
'passphrase:decrypt "iterations=1000000&cipherText=9b1c60&iv=5c8843f52ed3c0f2aa0086b0&salt=2240b7f1aa9c899894e528cf5b600e9c&tag=23c01112134317a63bcf3d41ea74e83b&version=1"', | ||
]; | ||
|
||
static flags = { | ||
password: flagParser.string(commonFlags.password), | ||
}; | ||
|
||
async run(): Promise<void> { | ||
const { | ||
args, | ||
flags: { password: passwordSource }, | ||
} = this.parse(DecryptCommand); | ||
|
||
const { encryptedPassphrase }: Args = args; | ||
|
||
const password = passwordSource ?? (await getPassphraseFromPrompt('password', true)); | ||
|
||
const result = processInputs(password, encryptedPassphrase as string); | ||
|
||
this.printJSON(result); | ||
} | ||
|
||
public printJSON(message?: object, pretty = false): void { | ||
if (pretty) { | ||
this.log(JSON.stringify(message, undefined, ' ')); | ||
} else { | ||
this.log(JSON.stringify(message)); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
/* | ||
* Copyright © 2020 Lisk Foundation | ||
* | ||
* See the LICENSE file at the top-level directory of this distribution | ||
* for licensing information. | ||
* | ||
* Unless otherwise agreed in a custom licensing agreement with the Lisk Foundation, | ||
* no part of this software, including this file, may be copied, modified, | ||
* propagated, or distributed except according to the terms contained in the | ||
* LICENSE file. | ||
* | ||
* Removal or modification of this copyright notice is prohibited. | ||
* | ||
*/ | ||
import { cryptography } from 'lisk-sdk'; | ||
import { flags as flagParser, Command } from '@oclif/command'; | ||
|
||
import { flags as commonFlags } from '../../utils/flags'; | ||
import { getPassphraseFromPrompt } from '../../utils/reader'; | ||
|
||
const outputPublicKeyOptionDescription = | ||
'Includes the public key in the output. This option is provided for the convenience of node operators.'; | ||
|
||
const processInputs = (passphrase: string, password: string, outputPublicKey: boolean) => { | ||
const encryptedPassphraseObject = cryptography.encryptPassphraseWithPassword(passphrase, password); | ||
const encryptedPassphrase = cryptography.stringifyEncryptedPassphrase(encryptedPassphraseObject); | ||
|
||
return outputPublicKey | ||
? { | ||
encryptedPassphrase, | ||
publicKey: cryptography.getKeys(passphrase).publicKey.toString('base64'), | ||
} | ||
: { encryptedPassphrase }; | ||
}; | ||
|
||
export default class EncryptCommand extends Command { | ||
static description = ` | ||
Encrypts your secret passphrase under a password. | ||
`; | ||
|
||
static examples = ['passphrase:encrypt']; | ||
|
||
static flags = { | ||
password: flagParser.string(commonFlags.password), | ||
passphrase: flagParser.string(commonFlags.passphrase), | ||
outputPublicKey: flagParser.boolean({ | ||
description: outputPublicKeyOptionDescription, | ||
}), | ||
}; | ||
|
||
async run(): Promise<void> { | ||
const { | ||
flags: { passphrase: passphraseSource, password: passwordSource, outputPublicKey }, | ||
} = this.parse(EncryptCommand); | ||
|
||
const passphrase = passphraseSource ?? (await getPassphraseFromPrompt('passphrase', true)); | ||
const password = passwordSource ?? (await getPassphraseFromPrompt('password', true)); | ||
const result = processInputs(passphrase, password, outputPublicKey); | ||
|
||
this.printJSON(result); | ||
} | ||
|
||
public printJSON(message?: object, pretty = false): void { | ||
if (pretty) { | ||
this.log(JSON.stringify(message, undefined, ' ')); | ||
} else { | ||
this.log(JSON.stringify(message)); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
/* | ||
* Copyright © 2020 Lisk Foundation | ||
* | ||
* See the LICENSE file at the top-level directory of this distribution | ||
* for licensing information. | ||
* | ||
* Unless otherwise agreed in a custom licensing agreement with the Lisk Foundation, | ||
* no part of this software, including this file, may be copied, modified, | ||
* propagated, or distributed except according to the terms contained in the | ||
* LICENSE file. | ||
* | ||
* Removal or modification of this copyright notice is prohibited. | ||
* | ||
*/ | ||
// eslint-disable-next-line max-classes-per-file | ||
export class FileSystemError extends Error { | ||
public constructor(message: string) { | ||
super(message); | ||
this.message = message; | ||
this.name = 'FileSystemError'; | ||
} | ||
} | ||
|
||
export class ValidationError extends Error { | ||
public constructor(message: string) { | ||
super(message); | ||
this.message = message; | ||
this.name = 'ValidationError'; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
/* | ||
* Copyright © 2020 Lisk Foundation | ||
* | ||
* See the LICENSE file at the top-level directory of this distribution | ||
* for licensing information. | ||
* | ||
* Unless otherwise agreed in a custom licensing agreement with the Lisk Foundation, | ||
* no part of this software, including this file, may be copied, modified, | ||
* propagated, or distributed except according to the terms contained in the | ||
* LICENSE file. | ||
* | ||
* Removal or modification of this copyright notice is prohibited. | ||
* | ||
*/ | ||
|
||
import * as liskPassphrase from '@liskhq/lisk-passphrase'; | ||
import * as inquirer from 'inquirer'; | ||
|
||
import { ValidationError } from './error'; | ||
|
||
interface MnemonicError { | ||
readonly code: string; | ||
readonly message: string; | ||
} | ||
|
||
const capitalise = (text: string): string => `${text.charAt(0).toUpperCase()}${text.slice(1)}`; | ||
|
||
const getPassphraseVerificationFailError = (displayName: string): string => | ||
`${capitalise(displayName)} was not successfully repeated.`; | ||
|
||
|
||
export const getPassphraseFromPrompt = async ( | ||
displayName = 'passphrase', | ||
shouldConfirm = false, | ||
): Promise<string> => { | ||
const questions = [ | ||
{ | ||
type: 'password', | ||
name: 'passphrase', | ||
message: `Please enter ${displayName}: `, | ||
}, | ||
]; | ||
if (shouldConfirm) { | ||
questions.push({ | ||
type: 'password', | ||
name: 'passphraseRepeat', | ||
message: `Please re-enter ${displayName}: `, | ||
}); | ||
} | ||
|
||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment | ||
const { passphrase, passphraseRepeat } = await inquirer.prompt(questions); | ||
|
||
if (!passphrase || (shouldConfirm && passphrase !== passphraseRepeat)) { | ||
throw new ValidationError(getPassphraseVerificationFailError(displayName)); | ||
} | ||
|
||
const passphraseErrors = [passphrase] | ||
.filter(Boolean) | ||
.map(pass => | ||
liskPassphrase.validation | ||
.getPassphraseValidationErrors(pass as string) | ||
.filter((error: MnemonicError) => error.message), | ||
); | ||
|
||
passphraseErrors.forEach(errors => { | ||
if (errors.length > 0) { | ||
const passphraseWarning = errors | ||
.filter((error: MnemonicError) => error.code !== 'INVALID_MNEMONIC') | ||
.reduce( | ||
(accumulator: string, error: MnemonicError) => | ||
accumulator.concat(`${error.message.replace(' Please check the passphrase.', '')} `), | ||
'Warning: ', | ||
); | ||
console.warn(passphraseWarning); | ||
} | ||
}); | ||
|
||
// eslint-disable-next-line @typescript-eslint/no-unsafe-return | ||
return passphrase; | ||
}; |
Oops, something went wrong.