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. Once the library has been installed, 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 called the registerValidator() function of the SSV contract) and their address.
List of operator IDs to register the validator to.
[12, 34, 56, 78]
Keystore file
Validator keystore filed create when generating the validator. You must provide the path to this.
./path-to-keystore.json'
Keystore Password
Password for the attached keystore file.
'XYZ'
Owner Address
Address of the validator Owner. (Wallet you used to login on the webapp).
'0x81592c3de184a3e2c0dcb5a261bc107bfa91f494'
Owner Nonce
Nonce of the validator Owner. (Wallet you used to login on the webapp). This can be obtained by using the SSV Subgraph, or the SSV Scanner SDK.
1
Example
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 toolconstTEST_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();
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.
It's important to note, that to generate multiple keyshares, developers should simply use the example above as a reference, and instead of processing one single keystore file at a time, write a simple loop that will iterate over multiple files, generate keyshares, and finally write all the generated keyshares into a single file with the format described above.