The SSV Keys SDK enable users to split a validator key into a predefined threshold of shares via Shamir-Secret-Sharing (SSS), and encrypts them with a set of operator keys.
In addition to the generation of shares, the tool also uses the validator key to sign the validator’s owner address and his registration nonce to ensure that only the legitimate owner of the validator can register it to the network.
The shares and the signature are constructed as sharesData which is used during validator registration through the SSV smart contract in order to facilitate their distribution from stakers to operators.
Please note that shares can be shared publicly since only assigned operators are able to decrypt them.
Installation
The SSV Keys library is distributed as a Javascript package, and it can be directly installed with the npm command (or yarn, if you prefer) using the repository's URL:
npmihttps://github.com/ssvlabs/ssv-keys.git
The library is not yet maintained in a stable manner on npmjs.com so for the time being, it is advised to use repository's URL, until this documentation page is updated to indicate otherwise.
Import library
The two main components of the ssv-keys library are the SSVKeys and KeyShares objects. Onche the library has been intstalled, they can be imported like so:
Using the ssv-keys SDK can be simply summarized in 3 steps:
Reading the keystore file (validator set of keys), to extract public and private keys
Build the keyshares configuration, given the chosen operators to which the validator will be distributed
Build the payload for the web3 transaction, and add it to the newly created keyshare file.
A requirement for splitting the keys of a validator, is to know the validator key owner's nonce (how many times this address has callded the registerValidator() function of the SSV contract) and their address.
Here's an example highlighting the 4 steps described above:
const { SSVKeys,KeyShares,KeySharesItem,SSVKeysException } =require('ssv-keys');constpath=require('path');constfsp=require('fs').promises;// These would probably come from your DAppconstoperatorKeys= ["LS0tLS1CRUdJTi...","LS0tLS1CRUdJTi...","LS0tLS1CRUdJTi...","LS0tLS1CRUdJTi..."];constoperatorIds= [12,34,56,78];// These can be either provided by the user (Staking-as-a-Service) or auto-generated (Staking Pool)constkeystore=require('./path-to-keystore.json');constkeystorePassword='XYZ';// The nonce of the owner within the SSV contract (increments after each validator registration), obtained using the ssv-scanner tool
constTEST_OWNER_NONCE=1;// The cluster owner addressconstTEST_OWNER_ADDRESS='0x81592c3de184a3e2c0dcb5a261bc107bfa91f494';constgetKeySharesFilePath= () => {return`${path.join(process.cwd(),'data')}${path.sep}keyshares.json`;};/** * This is more complex example demonstrating usage of SSVKeys SDK together with * KeyShares file which can be useful in a different flows for solo staker, staking provider or web developer. */asyncfunctionmain() {// 1. Initialize SSVKeys SDK and read the keystore fileconstssvKeys=newSSVKeys();const { publicKey,privateKey } =awaitssvKeys.extractKeys(keystore, keystorePassword);constoperators=operatorKeys.map((operatorKey, index) => ({ id: operatorIds[index], operatorKey, }));// 2. Build shares from operator IDs and public keysconstencryptedShares=awaitssvKeys.buildShares(privateKey, operators);constkeySharesItem=newKeySharesItem();awaitfsp.writeFile(getKeySharesFilePath(1),keySharesItem.toJson(), { encoding:'utf-8' });awaitkeySharesItem.update({ operators });awaitfsp.writeFile(getKeySharesFilePath(2),keySharesItem.toJson(), { encoding:'utf-8' });awaitkeySharesItem.update({ ownerAddress:TEST_OWNER_ADDRESS, ownerNonce:TEST_OWNER_NONCE, publicKey });awaitfsp.writeFile(getKeySharesFilePath(3),keySharesItem.toJson(), { encoding:'utf-8' });// 3. Build final web3 transaction payload and update keyshares file with payload dataawaitkeySharesItem.buildPayload({ publicKey, operators, encryptedShares, }, { ownerAddress:TEST_OWNER_ADDRESS, ownerNonce:TEST_OWNER_NONCE, privateKey });constkeyShares=newKeyShares();keyShares.add(keySharesItem);// Most times, you'd want to save the result in a fileawaitfsp.writeFile(getKeySharesFilePath(4),keyShares.toJson(), { encoding:'utf-8' });}voidmain();
Here's an example of the printed output:
It is important to notice that this format is a breaking change with respect to previous versions of ssv-keys as it contains an array ("shares") of shares data, where each single item represents one validator key, its related keyshares, and the payload necessary for the on-chain transaction.
This is true also in the case of a single validator key, where the array will only contain one item. It was done so, to maintain consistency across various use cases.
Thanks to the ssv-keys, it's possible to programmatically split a set of validator keys into key-shares, which can be then given to the operators assigned to run the distributed validator.
This is the foundation for building complex products like Staking Services or Staking Pools on top of SSV.