diff --git a/commander/src/bootstrapping/commands/keys/create.ts b/commander/src/bootstrapping/commands/keys/create.ts index 4765303e6cd..e47cd8dbd45 100644 --- a/commander/src/bootstrapping/commands/keys/create.ts +++ b/commander/src/bootstrapping/commands/keys/create.ts @@ -14,7 +14,7 @@ */ import { codec } from '@liskhq/lisk-codec'; -import { bls, address as addressUtil, ed, encrypt } from '@liskhq/lisk-cryptography'; +import { bls, address as addressUtil, ed, encrypt, legacy } from '@liskhq/lisk-cryptography'; import { Command, Flags as flagParser } from '@oclif/core'; import * as fs from 'fs-extra'; import * as path from 'path'; @@ -60,6 +60,9 @@ export class CreateCommand extends Command { description: 'Chain id', default: 0, }), + 'add-legacy': flagParser.boolean({ + description: 'Add legacy key derivation path to the result', + }), }; async run(): Promise { @@ -72,6 +75,7 @@ export class CreateCommand extends Command { count, offset, chainid, + 'add-legacy': addLegacy, }, } = await this.parse(CreateCommand); @@ -86,7 +90,37 @@ export class CreateCommand extends Command { } const keys = []; - for (let i = 0; i < count; i += 1) { + let i = 0; + if (addLegacy) { + const legacyKeyPath = 'legacy'; + const { privateKey: accountPrivateKey, publicKey: accountPublicKey } = + legacy.getPrivateAndPublicKeyFromPassphrase(passphrase); + const address = addressUtil.getAddressFromPublicKey(accountPublicKey); + const generatorPrivateKey = accountPrivateKey; + const generatorPublicKey = ed.getPublicKeyFromPrivateKey(generatorPrivateKey); + const blsKeyPath = `m/12381/134/${chainid}/99999`; + const blsPrivateKey = await bls.getPrivateKeyFromPhraseAndPath(passphrase, blsKeyPath); + const blsPublicKey = bls.getPublicKeyFromPrivateKey(blsPrivateKey); + const result = await this._createEncryptedObject( + { + address, + keyPath: legacyKeyPath, + accountPrivateKey, + accountPublicKey, + generatorKeyPath: legacyKeyPath, + generatorPrivateKey, + generatorPublicKey, + blsKeyPath, + blsPrivateKey, + blsPublicKey, + password, + }, + noEncrypt, + ); + keys.push(result); + i += 1; + } + for (; i < count; i += 1) { const accountKeyPath = `m/44'/134'/${offset + i}'`; const generatorKeyPath = `m/25519'/134'/${chainid}'/${offset + i}'`; const blsKeyPath = `m/12381/134/${chainid}/${offset + i}`; @@ -102,37 +136,23 @@ export class CreateCommand extends Command { const blsPrivateKey = await bls.getPrivateKeyFromPhraseAndPath(passphrase, blsKeyPath); const blsPublicKey = bls.getPublicKeyFromPrivateKey(blsPrivateKey); - let encryptedMessageObject = {}; - if (!noEncrypt) { - const plainGeneratorKeyData = { - generatorKey: generatorPublicKey, + const result = await this._createEncryptedObject( + { + address, + keyPath: accountKeyPath, + accountPrivateKey, + accountPublicKey, + generatorKeyPath, generatorPrivateKey, - blsKey: blsPublicKey, + generatorPublicKey, + blsKeyPath, blsPrivateKey, - }; - const encodedGeneratorKeys = codec.encode(plainGeneratorKeysSchema, plainGeneratorKeyData); - encryptedMessageObject = await encrypt.encryptMessageWithPassword( - encodedGeneratorKeys, + blsPublicKey, password, - ); - } - - keys.push({ - address: addressUtil.getLisk32AddressFromAddress(address), - keyPath: accountKeyPath, - publicKey: accountPublicKey.toString('hex'), - privateKey: accountPrivateKey.toString('hex'), - plain: { - generatorKeyPath, - generatorKey: generatorPublicKey.toString('hex'), - generatorPrivateKey: generatorPrivateKey.toString('hex'), - blsKeyPath, - blsKey: blsPublicKey.toString('hex'), - blsProofOfPossession: bls.popProve(blsPrivateKey).toString('hex'), - blsPrivateKey: blsPrivateKey.toString('hex'), }, - encrypted: encryptedMessageObject, - }); + noEncrypt, + ); + keys.push(result); } if (output) { @@ -141,4 +161,52 @@ export class CreateCommand extends Command { this.log(JSON.stringify({ keys }, undefined, ' ')); } } + private async _createEncryptedObject( + input: { + address: Buffer; + keyPath: string; + accountPublicKey: Buffer; + accountPrivateKey: Buffer; + generatorKeyPath: string; + generatorPublicKey: Buffer; + generatorPrivateKey: Buffer; + blsKeyPath: string; + blsPublicKey: Buffer; + blsPrivateKey: Buffer; + password: string; + }, + noEncrypt: boolean, + ) { + let encryptedMessageObject = {}; + if (!noEncrypt) { + const plainGeneratorKeyData = { + generatorKey: input.generatorPublicKey, + generatorPrivateKey: input.generatorPrivateKey, + blsKey: input.blsPublicKey, + blsPrivateKey: input.blsPrivateKey, + }; + const encodedGeneratorKeys = codec.encode(plainGeneratorKeysSchema, plainGeneratorKeyData); + encryptedMessageObject = await encrypt.encryptMessageWithPassword( + encodedGeneratorKeys, + input.password, + ); + } + + return { + address: addressUtil.getLisk32AddressFromAddress(input.address), + keyPath: input.keyPath, + publicKey: input.accountPublicKey.toString('hex'), + privateKey: input.accountPrivateKey.toString('hex'), + plain: { + generatorKeyPath: input.generatorKeyPath, + generatorKey: input.generatorPublicKey.toString('hex'), + generatorPrivateKey: input.generatorPrivateKey.toString('hex'), + blsKeyPath: input.blsKeyPath, + blsKey: input.blsPublicKey.toString('hex'), + blsProofOfPossession: bls.popProve(input.blsPrivateKey).toString('hex'), + blsPrivateKey: input.blsPrivateKey.toString('hex'), + }, + encrypted: encryptedMessageObject, + }; + } } diff --git a/commander/test/bootstrapping/commands/keys/create.spec.ts b/commander/test/bootstrapping/commands/keys/create.spec.ts index fe0b6c0ab8f..3b1bd4c6e6f 100644 --- a/commander/test/bootstrapping/commands/keys/create.spec.ts +++ b/commander/test/bootstrapping/commands/keys/create.spec.ts @@ -91,6 +91,7 @@ describe('keys:create command', () => { jest.spyOn(process.stdout, 'write').mockImplementation(val => stdout.push(val as string) > -1); jest.spyOn(process.stderr, 'write').mockImplementation(val => stderr.push(val as string) > -1); jest.spyOn(cryptography.ed, 'getPrivateKeyFromPhraseAndPath'); + jest.spyOn(cryptography.legacy, 'getPrivateAndPublicKeyFromPassphrase'); jest.spyOn(cryptography.ed, 'getPublicKeyFromPrivateKey'); jest.spyOn(cryptography.address, 'getAddressFromPublicKey'); jest.spyOn(cryptography.bls, 'getPrivateKeyFromPhraseAndPath'); @@ -176,6 +177,71 @@ describe('keys:create command', () => { }); }); + describe('keys:create --add-legacy --passphrase', () => { + it('should create valid keys', async () => { + const legacyKeys = + cryptography.legacy.getPrivateAndPublicKeyFromPassphrase(defaultPassphrase); + const legacyBLSKeyPath = 'm/12381/134/0/99999'; + const legacyBLSPrivateKey = await cryptography.bls.getPrivateKeyFromPhraseAndPath( + defaultPassphrase, + legacyBLSKeyPath, + ); + await CreateCommand.run(['--add-legacy', '--passphrase', defaultPassphrase], config); + const loggedData = JSON.parse(stdout[0]); + + expect(cryptography.legacy.getPrivateAndPublicKeyFromPassphrase).toHaveBeenCalledWith( + defaultPassphrase, + ); + expect(cryptography.address.getAddressFromPublicKey).toHaveBeenCalledWith( + legacyKeys.publicKey, + ); + expect(cryptography.ed.getPublicKeyFromPrivateKey).not.toHaveBeenCalledWith( + defaultAccountPrivateKey, + ); + expect(cryptography.ed.getPrivateKeyFromPhraseAndPath).not.toHaveBeenCalledWith( + defaultPassphrase, + defaultGeneratorKeyPath, + ); + expect(cryptography.ed.getPublicKeyFromPrivateKey).not.toHaveBeenCalledWith( + defaultGeneratorPrivateKey, + ); + expect(cryptography.bls.getPrivateKeyFromPhraseAndPath).toHaveBeenCalledWith( + defaultPassphrase, + legacyBLSKeyPath, + ); + expect(cryptography.bls.getPublicKeyFromPrivateKey).toHaveBeenCalledWith(legacyBLSPrivateKey); + expect(readerUtils.getPassphraseFromPrompt).not.toHaveBeenCalledWith('passphrase', true); + expect(readerUtils.getPasswordFromPrompt).toHaveBeenCalledWith('password', true); + + expect(loggedData).toMatchObject({ + keys: [ + { + address: cryptography.address.getLisk32AddressFromPublicKey(legacyKeys.publicKey), + keyPath: 'legacy', + publicKey: legacyKeys.publicKey.toString('hex'), + privateKey: legacyKeys.privateKey.toString('hex'), + plain: { + generatorKeyPath: 'legacy', + generatorKey: legacyKeys.publicKey.toString('hex'), + generatorPrivateKey: legacyKeys.privateKey.toString('hex'), + blsKeyPath: legacyBLSKeyPath, + blsPrivateKey: legacyBLSPrivateKey.toString('hex'), + }, + }, + ], + }); + expect(loggedData.keys[0].encrypted).toBeDefined(); + expect(loggedData.keys[0].encrypted).toHaveProperty('ciphertext'); + expect(loggedData.keys[0].encrypted).toHaveProperty('mac'); + expect(loggedData.keys[0].encrypted).toHaveProperty('cipherparams'); + expect(loggedData.keys[0].encrypted).toHaveProperty('kdfparams'); + expect(loggedData.keys[0].encrypted.cipher).toBe('aes-128-gcm'); + expect(loggedData.keys[0].encrypted.kdf).toBe('argon2id'); + expect(loggedData.keys[0].encrypted.version).toBe('1'); + expect(consoleWarnSpy).toHaveBeenCalledTimes(0); + }); + }); + describe('keys:create --no-encrypt true', () => { it('should create valid keys', async () => { await CreateCommand.run(['--no-encrypt'], config);