Skip to content
This repository has been archived by the owner on Jun 11, 2024. It is now read-only.

Commit

Permalink
Add authorize endpoint to chain connector plugin (#8809)
Browse files Browse the repository at this point in the history
* 🌱 Add authorize endpoint to chain connector plugin

* 🐛 Update to throw and log error when key is not enabled
  • Loading branch information
shuse2 authored Aug 9, 2023
1 parent f28783e commit 1e9af20
Show file tree
Hide file tree
Showing 7 changed files with 137 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ import {
proveResponseJSONToObj,
} from './utils';

const { address, ed, encrypt } = cryptography;
const { address, ed } = cryptography;

interface Data {
readonly blockHeader: chain.BlockHeaderJSON;
Expand All @@ -92,6 +92,7 @@ type ModulesMetadata = [
export class ChainConnectorPlugin extends BasePlugin<ChainConnectorPluginConfig> {
public endpoint = new Endpoint();
public configSchema = configSchema;

private _chainConnectorPluginDB!: liskDB.Database;
private _chainConnectorStore!: ChainConnectorStore;
private _lastCertificate!: LastCertificate;
Expand All @@ -104,7 +105,6 @@ export class ChainConnectorPlugin extends BasePlugin<ChainConnectorPluginConfig>
private _receivingChainID!: Buffer;
private _isReceivingChainMainchain!: boolean;
private _registrationHeight!: number;
private _privateKey!: Buffer;
private _ccuSaveLimit!: number;

public get nodeModulePath(): string {
Expand All @@ -123,20 +123,12 @@ export class ChainConnectorPlugin extends BasePlugin<ChainConnectorPluginConfig>
this._isSaveCCU = this.config.isSaveCCU;
this._registrationHeight = this.config.registrationHeight;
this._ccuSaveLimit = this.config.ccuSaveLimit;
const { password, encryptedPrivateKey } = this.config;
if (password) {
const parsedEncryptedKey = encrypt.parseEncryptedMessage(encryptedPrivateKey);
this._privateKey = Buffer.from(
await encrypt.decryptMessageWithPassword(parsedEncryptedKey, password, 'utf-8'),
'hex',
);
}
}

public async load(): Promise<void> {
this._chainConnectorPluginDB = await getDBInstance(this.dataPath);
this._chainConnectorStore = new ChainConnectorStore(this._chainConnectorPluginDB);
this.endpoint.load(this._chainConnectorStore);
this.endpoint.load(this.config, this._chainConnectorStore);

this._sendingChainClient = this.apiClient;
this._ownChainID = Buffer.from(this.appConfig.genesis.chainID, 'hex');
Expand Down Expand Up @@ -231,6 +223,7 @@ export class ChainConnectorPlugin extends BasePlugin<ChainConnectorPluginConfig>
}
} catch (error) {
this.logger.info(
{ err: error },
`Error occured while submitting CCU for the blockHeader at height: ${newBlockHeader.height}`,
);
return;
Expand Down Expand Up @@ -722,8 +715,10 @@ export class ChainConnectorPlugin extends BasePlugin<ChainConnectorPluginConfig>
}

private async _submitCCU(ccuParams: Buffer): Promise<void> {
const relayerPrivateKey = this._privateKey;
const relayerPublicKey = ed.getPublicKeyFromPrivateKey(relayerPrivateKey);
if (!this._chainConnectorStore.privateKey) {
throw new Error('There is no key enabled to submit CCU');
}
const relayerPublicKey = ed.getPublicKeyFromPrivateKey(this._chainConnectorStore.privateKey);
const targetCommand = this._isReceivingChainMainchain
? COMMAND_NAME_SUBMIT_MAINCHAIN_CCU
: COMMAND_NAME_SUBMIT_SIDECHAIN_CCU;
Expand All @@ -749,7 +744,7 @@ export class ChainConnectorPlugin extends BasePlugin<ChainConnectorPluginConfig>
params: ccuParams,
signatures: [],
});
tx.sign(chainID, relayerPrivateKey);
tx.sign(chainID, this._chainConnectorStore.privateKey);
let result: { transactionId: string };
if (this._isSaveCCU) {
result = { transactionId: tx.id.toString('hex') };
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
* Removal or modification of this copyright notice is prohibited.
*/

import { codec, db as liskDB, AggregateCommit, chain } from 'lisk-sdk';
import { codec, db as liskDB, AggregateCommit, chain, cryptography } from 'lisk-sdk';
import * as os from 'os';
import { join } from 'path';
import { ensureDir } from 'fs-extra';
Expand Down Expand Up @@ -71,6 +71,7 @@ export const checkDBError = (error: Error | unknown) => {

export class ChainConnectorStore {
private readonly _db: KVStore;
private _privateKey?: Buffer;

public constructor(db: KVStore) {
this._db = db;
Expand All @@ -80,6 +81,10 @@ export class ChainConnectorStore {
this._db.close();
}

public get privateKey(): Buffer | undefined {
return this._privateKey;
}

public async getBlockHeaders(): Promise<BlockHeader[]> {
let blockHeaders: BlockHeader[] = [];
try {
Expand Down Expand Up @@ -193,4 +198,18 @@ export class ChainConnectorStore {

await this._db.set(DB_KEY_LIST_OF_CCU, codec.encode(listOfCCUsSchema, { listOfCCUs }));
}

public async setPrivateKey(encryptedPrivateKey: string, password: string) {
const parsedEncryptedKey = cryptography.encrypt.parseEncryptedMessage(encryptedPrivateKey);
this._privateKey = Buffer.from(
await cryptography.encrypt.decryptMessageWithPassword(parsedEncryptedKey, password, 'utf-8'),
'hex',
);
}

public async deletePrivateKey(encryptedPrivateKey: string, password: string) {
const parsedEncryptedKey = cryptography.encrypt.parseEncryptedMessage(encryptedPrivateKey);
await cryptography.encrypt.decryptMessageWithPassword(parsedEncryptedKey, password, 'utf-8');
this._privateKey = undefined;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,21 +18,30 @@ import {
chain,
BlockHeader,
BlockHeaderJSON,
validator as liskValidator,
} from 'lisk-sdk';
import { ChainConnectorStore } from './db';
import {
AggregateCommitJSON,
CCMsFromEventsJSON,
ChainConnectorPluginConfig,
LastSentCCMWithHeightJSON,
SentCCUsJSON,
ValidatorsDataJSON,
} from './types';
import { aggregateCommitToJSON, ccmsFromEventsToJSON, validatorsHashPreimagetoJSON } from './utils';
import { authorizeRequestSchema } from './schemas';

// disabled for type annotation
// eslint-disable-next-line prefer-destructuring
const validator: liskValidator.LiskValidator = liskValidator.validator;

export class Endpoint extends BasePluginEndpoint {
private _chainConnectorStore!: ChainConnectorStore;
private _config!: ChainConnectorPluginConfig;

public load(store: ChainConnectorStore) {
public load(config: ChainConnectorPluginConfig, store: ChainConnectorStore) {
this._config = config;
this._chainConnectorStore = store;
}

Expand Down Expand Up @@ -86,4 +95,27 @@ export class Endpoint extends BasePluginEndpoint {
const validatorsHashPreimage = await this._chainConnectorStore.getValidatorsHashPreimage();
return validatorsHashPreimagetoJSON(validatorsHashPreimage);
}

// eslint-disable-next-line @typescript-eslint/require-await
public async authorize(context: PluginEndpointContext): Promise<{ result: string }> {
validator.validate<{ enable: boolean; password: string }>(
authorizeRequestSchema,
context.params,
);

const { enable, password } = context.params;
const result = `Successfully ${enable ? 'enabled' : 'disabled'} the chain connector plugin.`;

if (!enable) {
await this._chainConnectorStore.deletePrivateKey(this._config.encryptedPrivateKey, password);
return {
result,
};
}

await this._chainConnectorStore.setPrivateKey(this._config.encryptedPrivateKey, password);
return {
result,
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,6 @@ export const configSchema = {
format: 'uint64',
description: 'Fee to be paid for each CCU transaction',
},
password: {
type: 'string',
description: 'Password to decrypt encryptedPrivateKey',
},
isSaveCCU: {
type: 'boolean',
description:
Expand All @@ -77,7 +73,7 @@ export const configSchema = {
description: 'Chain ID of the receiving chain.',
},
},
required: ['ccuFee', 'encryptedPrivateKey', 'password', 'receivingChainID'],
required: ['ccuFee', 'encryptedPrivateKey', 'receivingChainID'],
default: {
ccuFrequency: CCU_FREQUENCY,
isSaveCCU: false,
Expand Down Expand Up @@ -218,3 +214,17 @@ export const ccmsFromEventsSchema = {
},
},
};

export const authorizeRequestSchema = {
$id: '/lisk/chainConnector/authorizeRequest',
type: 'object',
required: ['password', 'enable'],
properties: {
password: {
type: 'string',
},
enable: {
type: 'boolean',
},
},
};
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ export interface ChainConnectorPluginConfig {
ccuFrequency: number;
encryptedPrivateKey: string;
ccuFee: string;
password: string;
isSaveCCU: boolean;
maxCCUSize: number;
registrationHeight: number;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,13 @@ describe('endpoints', () => {
const encryptedKey = await cryptography.encrypt.encryptMessageWithPassword(
Buffer.from(defaultPrivateKey, 'hex'),
defaultPassword,
{
kdfparams: {
iterations: 1,
memorySize: 256,
parallelism: 1,
},
},
);
const defaultEncryptedPrivateKey = cryptography.encrypt.stringifyEncryptedMessage(encryptedKey);

Expand Down Expand Up @@ -300,4 +307,48 @@ describe('endpoints', () => {
expect(response).toStrictEqual(lastSentCCMJSON);
});
});

describe('authorize', () => {
it('should reject when invalid params is given', async () => {
await expect(chainConnectorPlugin.endpoint.authorize({ params: {} } as any)).rejects.toThrow(
"must have required property 'password'",
);
});

it('should enable when correct password is given', async () => {
await expect(
chainConnectorPlugin.endpoint.authorize({
params: { enable: true, password: defaultPassword },
} as any),
).resolves.toEqual({
result: 'Successfully enabled the chain connector plugin.',
});
});

it('should not enable when incorrect password is given', async () => {
await expect(
chainConnectorPlugin.endpoint.authorize({
params: { enable: true, password: 'invalid' },
} as any),
).rejects.toThrow('Unsupported state or unable to authenticate data');
});

it('should not disable when incorrect password is given', async () => {
await expect(
chainConnectorPlugin.endpoint.authorize({
params: { enable: false, password: defaultPassword },
} as any),
).resolves.toEqual({
result: 'Successfully disabled the chain connector plugin.',
});
});

it('should disable when incorrect password is given', async () => {
await expect(
chainConnectorPlugin.endpoint.authorize({
params: { enable: false, password: 'invalid' },
} as any),
).rejects.toThrow('Unsupported state or unable to authenticate data');
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -187,9 +187,18 @@ describe('ChainConnectorPlugin', () => {
};
chainConnectorPlugin = new plugins.ChainConnectorPlugin();

(chainConnectorStoreMock as any).privateKey = Buffer.from(defaultPrivateKey, 'hex');

const encryptedKey = await cryptography.encrypt.encryptMessageWithPassword(
Buffer.from(defaultPrivateKey, 'hex'),
defaultPassword,
{
kdfparams: {
iterations: 1,
memorySize: 256,
parallelism: 1,
},
},
);
defaultEncryptedPrivateKey = cryptography.encrypt.stringifyEncryptedMessage(encryptedKey);

Expand Down

0 comments on commit 1e9af20

Please sign in to comment.