Skip to content

Commit

Permalink
new logic
Browse files Browse the repository at this point in the history
  • Loading branch information
vadiminc committed Dec 4, 2023
1 parent 35b1875 commit 7bd4746
Show file tree
Hide file tree
Showing 6 changed files with 280 additions and 0 deletions.
19 changes: 19 additions & 0 deletions src/commands/actions/arguments/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import keystoreArgument from './keystore';
import keystorePathArgument from './keystore-path';
import ownerNonceArgument from './owner-nonce';
import operatorIdsArgument from './operator-ids';
import ownerAddressArgument from './owner-address';
import keystorePasswordArgument from './password';
import outputFolderArgument from './output-folder';
import operatorPublicKeysArgument from './operator-public-keys';

export {
keystoreArgument,
keystorePathArgument,
ownerNonceArgument,
operatorIdsArgument,
ownerAddressArgument,
keystorePasswordArgument,
outputFolderArgument,
operatorPublicKeysArgument
};
32 changes: 32 additions & 0 deletions src/commands/actions/arguments/keystore.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { fileExistsValidator, jsonFileValidator, sanitizePath } from '../validators';

/**
* Keystore argument validates if keystore file exists and is valid keystore file.
*/
export default {
arg1: '-ks',
arg2: '--keystore',
options: {
required: false,
type: String,
help: 'The validator keystore file path. Only one keystore file can be specified using this argument'
},
interactive: {
options: {
type: 'text',
message: 'Provide the keystore file path',
validateSingle: (filePath: string): any => {
filePath = sanitizePath(String(filePath).trim());
let isValid = fileExistsValidator(filePath);
if (isValid !== true) {
return isValid;
}
isValid = jsonFileValidator(filePath);
if (isValid !== true) {
return isValid;
}
return true;
},
}
}
};
13 changes: 13 additions & 0 deletions src/commands/actions/validators/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { sanitizePath, fileExistsValidator, jsonFileValidator } from './file';
import { keystorePasswordValidator } from './keystore-password';
import { isOperatorsLengthValid } from "./operator-ids";
import { operatorPublicKeyValidator } from './operator';

export {
sanitizePath,
jsonFileValidator,
fileExistsValidator,
isOperatorsLengthValid,
keystorePasswordValidator,
operatorPublicKeyValidator,
}
15 changes: 15 additions & 0 deletions src/lib/KeyShares/KeySharesData/validators/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { OpeatorsListValidator } from './operator-unique';
import { PublicKeyValidator } from './public-key';
import { OwnerAddressValidator } from './owner-address';
import { OwnerNonceValidator } from './owner-nonce';
import { MatchLengthValidator } from './match';
import { OpeatorPublicKeyValidator } from './operator-public-key';

export {
OpeatorsListValidator,
PublicKeyValidator,
OwnerAddressValidator,
OwnerNonceValidator,
MatchLengthValidator,
OpeatorPublicKeyValidator,
}
193 changes: 193 additions & 0 deletions src/lib/KeyShares/KeySharesItem.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
import * as ethers from 'ethers';
import * as web3Helper from '../helpers/web3.helper';
import {
IsOptional,
ValidateNested,
validateSync
} from 'class-validator';

import { KeySharesData } from './KeySharesData/KeySharesData';
import { KeySharesPayload } from './KeySharesData/KeySharesPayload';
import { EncryptShare } from '../Encryption/Encryption';
import { IKeySharesPartitialData } from './KeySharesData/IKeySharesData';
import { IOperator } from './KeySharesData/IOperator';
import { operatorSortedList } from '../helpers/operator.helper';
import { OwnerAddressFormatError, OwnerNonceFormatError } from '../exceptions/keystore';

export interface IKeySharesPayloadData {
publicKey: string,
operators: IOperator[],
encryptedShares: EncryptShare[],
}

export interface IKeySharesToSignatureData {
ownerAddress: string,
ownerNonce: number,
privateKey: string,
}

export interface IKeySharesFromSignatureData {
ownerAddress: string,
ownerNonce: number,
publicKey: string,
}

const SIGNATURE_LENGHT = 192;
const PUBLIC_KEY_LENGHT = 96;

/**
* Key shares file data interface.
*/
export class KeySharesItem {
@IsOptional()
@ValidateNested()
public data: KeySharesData;

@IsOptional()
@ValidateNested()
public payload: KeySharesPayload;

constructor() {
this.data = new KeySharesData();
this.payload = new KeySharesPayload();
}

/**
* Build payload from operators list, encrypted shares and validator public key
* @param publicKey
* @param operatorIds
* @param encryptedShares
*/
async buildPayload(metaData: IKeySharesPayloadData, toSignatureData: IKeySharesToSignatureData): Promise<any> {
const {
ownerAddress,
ownerNonce,
privateKey,
} = toSignatureData;

if (!Number.isInteger(ownerNonce) || ownerNonce < 0) {
throw new OwnerNonceFormatError(ownerNonce, 'Owner nonce is not positive integer');
}

let address;
try {
address = web3Helper.web3.utils.toChecksumAddress(ownerAddress);
} catch {
throw new OwnerAddressFormatError(ownerAddress, 'Owner address is not a valid Ethereum address');
}

const payload = this.payload.build({
publicKey: metaData.publicKey,
operatorIds: operatorSortedList(metaData.operators).map(operator => operator.id),
encryptedShares: metaData.encryptedShares,
});

const signature = await web3Helper.buildSignature(`${address}:${ownerNonce}`, privateKey);
const signSharesBytes = web3Helper.hexArrayToBytes([signature, payload.sharesData]);

payload.sharesData = `0x${signSharesBytes.toString('hex')}`;

// verify signature
await this.validateSingleShares(payload.sharesData, {
ownerAddress,
ownerNonce,
publicKey: await web3Helper.privateToPublicKey(privateKey),
});

return payload;
}


async validateSingleShares(shares: string, fromSignatureData: IKeySharesFromSignatureData): Promise<void> {
const { ownerAddress, ownerNonce, publicKey } = fromSignatureData;

if (!Number.isInteger(ownerNonce) || ownerNonce < 0) {
throw new OwnerNonceFormatError(ownerNonce, 'Owner nonce is not positive integer');
}

const address = web3Helper.web3.utils.toChecksumAddress(ownerAddress);
const signaturePt = shares.replace('0x', '').substring(0, SIGNATURE_LENGHT);

await web3Helper.validateSignature(`${address}:${ownerNonce}`, `0x${signaturePt}`, publicKey);
}

/**
* Build shares from bytes string and operators list length
* @param bytes
* @param operatorCount
*/
buildSharesFromBytes(bytes: string, operatorCount: number): any {
const sharesPt = bytes.replace('0x', '').substring(SIGNATURE_LENGHT);

const pkSplit = sharesPt.substring(0, operatorCount * PUBLIC_KEY_LENGHT);
const pkArray = ethers.utils.arrayify('0x' + pkSplit);
const sharesPublicKeys = this.splitArray(operatorCount, pkArray)
.map(item => ethers.utils.hexlify(item));

const eSplit = bytes.substring(operatorCount * PUBLIC_KEY_LENGHT);
const eArray = ethers.utils.arrayify('0x' + eSplit);
const encryptedKeys = this.splitArray(operatorCount, eArray).map(item =>
Buffer.from(ethers.utils.hexlify(item).replace('0x', ''), 'hex').toString(
'base64',
),
);

return { sharesPublicKeys, encryptedKeys };
}

/**
* Updates the current instance with partial data and payload, and validates.
* @param data Partial key shares data.
* @param payload Partial key shares payload.
*/
update(data: IKeySharesPartitialData): void {
this.data.update(data);
this.validate();
}

/**
* Validate everything
*/
validate(): any {
validateSync(this);
}

/**
* Initialise from JSON or object data.
*/
async fromJson(content: string | any): Promise<KeySharesItem> {
const body = typeof content === 'string' ? JSON.parse(content) : content;
this.data.update(body.data);
this.payload.update(body.payload);
this.validate();
// Custom validation: verify signature
await this.validateSingleShares(this.payload.sharesData, {
ownerAddress: this.data.ownerAddress as string,
ownerNonce: this.data.ownerNonce as number,
publicKey: this.data.publicKey as string,
});

return this;
}

/**
* Stringify key shares to be ready for saving in file.
*/
toJson(): string {
return JSON.stringify({
data: this.data || null,
payload: this.payload || null,
}, null, 2);
}

private splitArray(parts: number, arr: Uint8Array) {
const partLength = Math.floor(arr.length / parts);
const partsArr = [];
for (let i = 0; i < parts; i++) {
const start = i * partLength;
const end = start + partLength;
partsArr.push(arr.slice(start, end));
}
return partsArr;
}
}
8 changes: 8 additions & 0 deletions src/lib/exceptions/base.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export class BaseCustomError extends Error {
constructor(message: string) {
super(message);
this.name = this.constructor.name;
Error.captureStackTrace(this, this.constructor);
this.stack = `${this.name}: ${this.message}`; // Customizing stack
}
}

0 comments on commit 7bd4746

Please sign in to comment.