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.

160 lines (144 loc) 5.55 kB
// import atob from 'atob'; import bls from './BLS'; import Threshold, { IShares, ISharesKeyPairs } from './Threshold'; import EthereumKeyStore from './EthereumKeyStore/EthereumKeyStore'; import Encryption, { EncryptShare } from './Encryption/Encryption'; import { operatorSortedList } from './helpers/operator.helper'; import { IOperator } from './KeyShares/KeySharesData/IOperator'; import { KeySharesItem } from "./KeyShares/KeySharesItem"; export interface ExtractedKeys { privateKey: string; publicKey: string; } interface ISharesValidation { shares: string; operatorsCount: number; validatorPublicKey: string; isAccountExists: boolean; ownerAddress: string; ownerNonce: number; blockNumber: number; } /** * SSVKeys class provides high-level methods to easily work with entire flow: * - getting private key from keystore file using password * - creating shares threshold * - creating final shares * - building final payload which is ready to be used in web3 transaction */ export class SSVKeys { static SHARES_FORMAT_ABI = 'abi'; protected threshold: ISharesKeyPairs | undefined; /** * Extract private key from keystore data using keystore password. * Generally can be used in browsers when the keystore data has been provided by browser. * @param data * @param password */ async extractKeys(data: string, password: string): Promise<ExtractedKeys> { const privateKey = await new EthereumKeyStore(data).getPrivateKey(password); if (!bls.deserializeHexStrToSecretKey) { await bls.init(bls.BLS12_381); } return { privateKey: `0x${privateKey}`, publicKey: `0x${bls.deserializeHexStrToSecretKey(privateKey).getPublicKey().serializeToHexStr()}` }; } /** * Build threshold using private key and list of operators. * @param privateKey * @param operators */ async createThreshold(privateKey: string, operators: IOperator[]): Promise<ISharesKeyPairs> { const sortedOperators = operatorSortedList(operators); this.threshold = await new Threshold().create(privateKey, sortedOperators.map(item => item.id)); return this.threshold; } /** * Encrypt operators shares using operators list (id, publicKey). * @param operators * @param shares */ async encryptShares(operators: IOperator[], shares: IShares[]): Promise<EncryptShare[]> { const sortedOperators = operatorSortedList(operators); const decodedOperatorPublicKeys = sortedOperators.map(item => Buffer.from(item.operatorKey, 'base64').toString()); return new Encryption(decodedOperatorPublicKeys, shares).encrypt(); } /** * Build shares from private key, operators list * @param privateKey * @param operators */ async buildShares(privateKey: string, operators: IOperator[]): Promise<EncryptShare[]> { const threshold = await this.createThreshold(privateKey, operators); return this.encryptShares(operators, threshold.shares); } /** * Getting threshold if it has been created before. */ getThreshold() { return this.threshold; } async validateSharesPostRegistration({ shares, operatorsCount, validatorPublicKey, isAccountExists, ownerAddress, ownerNonce, blockNumber }: ISharesValidation) { const keySharesItem = new KeySharesItem(); let restoredSharesPublicKeys; let restoredSharesEncryptedKeys; let sharesError = ''; let sharesErrorMessage = ''; let signatureError = ''; let signatureErrorMessage = ''; let errorMessage = ''; try { const restoredShares = keySharesItem.buildSharesFromBytes(shares, operatorsCount); const { sharesPublicKeys, encryptedKeys } = restoredShares; restoredSharesPublicKeys = sharesPublicKeys; restoredSharesEncryptedKeys = encryptedKeys; } catch (e: any) { sharesError = e.stack || e.trace || e; sharesErrorMessage = e.message; errorMessage = 'Can not extract shares from bytes'; } if (!sharesError && !errorMessage) { const signatureData = { ownerNonce, publicKey: validatorPublicKey, ownerAddress }; try { await keySharesItem.validateSingleShares(shares, signatureData) } catch (e: any) { signatureError = e.stack || e.trace || e; signatureErrorMessage = e.message; errorMessage = 'Failed to validate single shares'; if (isAccountExists) { errorMessage += `. Account exist for owner address: ${ownerAddress}`; } else { errorMessage += `. Account is not synced for owner address: ${ownerAddress}`; } if (ownerNonce) { errorMessage += `. Used nonce: ${ownerNonce}`; } errorMessage += `. Signature Data: ${JSON.stringify(signatureData)}`; } } return { isValid: !sharesError && !signatureError && !errorMessage, isSharesValid: !sharesError, sharesPublicKeys: restoredSharesPublicKeys, encryptedKeys: restoredSharesEncryptedKeys, memo: !!sharesError || !!signatureError ? [{ message: errorMessage, error: sharesError || signatureError, data: `${sharesErrorMessage}${signatureErrorMessage ? '. ' + signatureErrorMessage : ''}`, blockNumber }] : [] } } }