UNPKG

ssv-keys

Version:

Tool for splitting a validator key into a predefined threshold of shares via Shamir-Secret-Sharing (SSS), and encrypt them with a set of operator keys.

132 lines (109 loc) 3.66 kB
import bls from '../BLS'; import { SecretKeyType } from 'bls-eth-wasm'; import { SSVKeysException } from '../exceptions/base'; import { PrivateKeyFormatError } from '../exceptions/keystore'; import { isOperatorsLengthValid } from '../../commands/actions/validators'; export interface IShares { privateKey: string, publicKey: string, id?: any } export interface ISharesKeyPairs { privateKey: string, publicKey: string, shares: IShares[] } export class ThresholdInvalidOperatorsLengthError extends SSVKeysException { public operators: number[]; // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types constructor(operators: number[], message: string) { super(message); this.operators = operators; } } export class ThresholdInvalidOperatorIdError extends SSVKeysException { public operator: any; // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types constructor(operator: any, message: string) { super(message); this.operator = operator; } } /** * Building threshold for list of operator IDs */ class Threshold { protected publicKey: any; protected privateKey: any; protected shares: Array<any> = []; static get DEFAULT_THRESHOLD_NUMBER(): number { return 3; } /** * Receives list of operators IDs. * len(operator IDs) := 3 * F + 1 * * If F calculated from this formula is not integer number - it will raise exception. * Generate keys and return promise */ async create(privateKeyString: string, operatorIds: number[]): Promise<ISharesKeyPairs> { if (!privateKeyString.startsWith('0x')) { throw new PrivateKeyFormatError(privateKeyString, 'The private key must be provided in the 0x format.') } // Validation operatorIds.map(operatorId => { if (!Number.isInteger(operatorId)) { throw new ThresholdInvalidOperatorIdError( operatorId, `Operator must be integer. Got: ${operatorId}` ); } }); if (!isOperatorsLengthValid(operatorIds.length)) { throw new ThresholdInvalidOperatorsLengthError( operatorIds, 'Invalid operators amount. Enter an 3f+1 compatible amount of operator ids.' ); } const msk = []; const mpk = []; if (!bls.deserializeHexStrToSecretKey) { await bls.init(bls.BLS12_381); } // Master key Polynomial this.privateKey = bls.deserializeHexStrToSecretKey(privateKeyString.replace('0x', '')); this.publicKey = this.privateKey.getPublicKey(); msk.push(this.privateKey); mpk.push(this.publicKey); const F = (operatorIds.length - 1) / 3; // Construct poly for (let i = 1; i < operatorIds.length - F; i += 1) { const sk: SecretKeyType = new bls.SecretKey(); sk.setByCSPRNG(); msk.push(sk); const pk = sk.getPublicKey(); mpk.push(pk); } // Evaluate shares - starting from 1 because 0 is master key for (const operatorId of operatorIds) { const id = new bls.Id(); id.setInt(operatorId); const shareSecretKey = new bls.SecretKey(); shareSecretKey.share(msk, id); const sharePublicKey = new bls.PublicKey(); sharePublicKey.share(mpk, id); this.shares.push({ privateKey: `0x${shareSecretKey.serializeToHexStr()}`, publicKey: `0x${sharePublicKey.serializeToHexStr()}`, id, }); } const response: ISharesKeyPairs = { privateKey: `0x${this.privateKey.serializeToHexStr()}`, publicKey: `0x${this.publicKey.serializeToHexStr()}`, shares: this.shares, }; return response; } } export default Threshold;