Skip to content

Commit

Permalink
Merge pull request #7 from bloxapp/leftovers
Browse files Browse the repository at this point in the history
Threshold (3f+1) and min/max/unique operators
  • Loading branch information
y0sher authored Sep 11, 2023
2 parents c00b9a3 + b1f516e commit f22c75f
Show file tree
Hide file tree
Showing 9 changed files with 151 additions and 96 deletions.
24 changes: 20 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,9 +1,27 @@
# This Makefile is meant to be used by people that do not usually work
# with Go source code. If you know what GOPATH is then you probably
# don't need to bother with make.

.PHONY: dkgcli test clean build docker-build

GOBIN = ./build/bin
GO ?= latest
GORUN = env GO111MODULE=on go run
GOINSTALL = env GO111MODULE=on go install -v
GOTEST = env GO111MODULE=on go test -v
# Name of the Go binary output
BINARY_NAME=./bin/dkgcli

# Docker image name
DOCKER_IMAGE=ssv-dkg-tool

install:
$(GOINSTALL) cmd/dkgcli/dkgcli.go
@echo "Done building."
@echo "Run dkgcli to launch the tool."

clean:
env GO111MODULE=on go clean -cache

# Recipe to compile the Go program
build:
@echo "Building Go binary..."
Expand All @@ -21,6 +39,4 @@ docker-build:

docker-demo:
@echo "Running docker compose demo"
docker-compose up

.PHONY: build docker-build
docker-compose up
114 changes: 60 additions & 54 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ The data of the operators (ID, IP, Pubkey) can be collected in any way, for exam
### Build

```sh
go build cmd/dkgcli/dkgcli.go
make install
```

### Server
Expand All @@ -21,60 +21,79 @@ Whenever the server receives a message it directs it to the right instance by th
Start a DKG server

```sh
./dkgcli start-dkg-server --privKey ./examples/server1/encrypted_private_key.json --port 3030 --password 12345678
dkgcli start-dkg-server --privKey ./examples/server1/encrypted_private_key.json --port 3030 --password 12345678 --storeShare true

### where
--privKey ./examples/server1/key # path to base 64 encoded RSA private key in PKCS #1, ASN.1 DER form.
--privKey ./encrypted_private_key.json # path to base 64 encoded RSA private key in PKCS #1, ASN.1 DER form.
--port 3030 # port for listening messages
--paseord 12345678 # password for encrypted keys
--password: 12345678 # password for encrypted keys
--storeShare # store created bls key share to a file for later reuse
```

Its also possible to use yaml configuration file `./config/operator.yaml` for parameters. `dkgcli` will be looking for this file at `./config/` folder.

Example:

```yaml
privKey: ./examples/server1/encrypted_private_key.json
privKey: ./encrypted_private_key.json
password: 12345678
port: 3030
storeShare: true
```
When using configuration file, run:
```sh
dkgcli start-dkg-server
```

### Initiator of DKG key generation

The initiator uses `ssv-dkg-init` to create the initial details needed to run DKG between all operators.
The initiator uses `init-dkg` to create the initial details needed to run DKG between all operators.

```sh
./dkgcli init-dkg \
dkgcli init-dkg \
--operatorIDs 1,2,3,4 \
--operatorsInfoPath ./examples/operators_integration.csv \
--operatorsInfoPath ./examples/operators_integration.json \
--owner 0x81592c3de184a3e2c0dcb5a261bc107bfa91f494 \
--nonce 1 \
--threshold 3 \
--withdrawPublicKey 0100000000000000000000001d2f14d2dffee594b4093d42e4bc1b0ea55e8aa7 \
--nonce 4 \
--withdrawAddress 0000000000000000000000000000000000000009 \
--fork 00000000
--depositResultsPath deposit.json
--ssvPayloadResultsPath payload.json
#### where
--operatorIDs 1,2,3,4 # operator IDs which will be used for a DKG ceremony
--operatorsInfoPath ./examples/operators_integration.csv # path to info about operators - ID,base64(RSA pub key),
--threshold 3 # threshold set for a master signature - if T out on N signatures provided the master signature will be recovered
--operatorsInfoPath ./examples/operators_integration.json # path to info about operators - ID,base64(RSA pub key),
--owner 0x81592c3de184a3e2c0dcb5a261bc107bfa91f494 # owner address for the SSV contract
--nonce 1 # owner nonce for the SSV contract
--nonce 4 # owner nonce for the SSV contract
--fork "00000000" # fork id bytes in HEX
--depositResultsPath # path to store the result file
--ssvPayloadResultsPath # path to store ssv contract payload file
```

Its also possible to use yaml configuration file `./config/initiator.yaml` for parameters. `dkgcli` will be looking for this file at `./config/` folder.

Example:

```yaml
threshold: 4
operatorIDs: [1, 2, 3, 4]
withdrawAddress: "0100000000000000000000001d2f14d2dffee594b4093d42e4bc1b0ea55e8aa7"
withdrawAddress: "0000000000000000000000000000000000000009"
owner: "0x81592c3de184a3e2c0dcb5a261bc107bfa91f494"
nonce: 4
fork: "00000000"
operatorsInfoPath: ./examples/operators_integration.csv
operatorsInfoPath: ./examples/operators_integration.json
depositResultsPath: ./deposit.json
ssvPayloadResultsPath: ./payload.json
```
When using configuration file, run:
```sh
dkgcli init-dkg
```

**_NOTE: Threshold is computed automatically using 3f+1 tolerance._**

### Generate RSA operator key

```sh
Expand Down Expand Up @@ -199,70 +218,57 @@ The DKG server can handle multiple DKG instances, it saves up to MaxInstances(10
- [x] output - signed ssv deposit data + encrypted shares for SSV contract
- [x] verification of ssv deposit data and encrypted shares
- [ ] existing validator public key resharing
- [ ] private key recreation from shares (in case of switch to a standard ETH validator)
- [ ] CLI for initiator and operators
- [ ] storage for initiator and keystore for operators
- [ ] more testing
- [ ] logging
- [x] CLI for initiator and operators
- [x] keystore for operators
- [x] more testing
- [x] logging

### Additional:

- [ ] get existing pub key share by ID from operators
- [ ] limit max of operators (T-threshold min/max)
- [x] limit max of operators (T-threshold min/max)
- [x] secure the communication between initiator and operators

### Flow TODO Brakedown

---

- [~70%] New key generation
- [~100%] New key generation

#### Round 1

- [x] CLI for initiator
- [x] CLI for operator
- [ ] RSA secret storage for both initiator and operator
- [ ] Init message:
- [x] RSA secret storage for operator
- [x] Init message:
- [x] Message sig validation
- [x] Init message owner + nonce fields. ID is random UUID
- [ ] Timeouts
- [ ] Error handling
- [ ] Exchange message:
- [x] Timeouts
- [x] Error handling
- [x] Exchange message:
- [x] Message sig validation
- [ ] Secret RSA key storage
- [ ] Timeouts
- [ ] Error handling
- [ ] Code refactoring
- [ ] Unit tests
- [ ] integration tests
- [x] Timeouts
- [x] Error handling
- [x] Code refactoring
- [x] Unit tests
- [x] integration tests

#### Round 2

- [x] Deal message:
- [x] Result message:
- [ ] Secure storage for key shares and DKG result (keystore + db) + recover option
- [x] Storage for key shares and DKG result
- [x] Validate signature shares + validator pub key + pub and encrypted shares at initiator
- [ ] Timeouts
- [ ] Code refactoring
- [ ] Error handling
- [ ] Unit tests
- [x] Timeouts
- [x] Code refactoring
- [x] Error handling
- [x] Unit tests

---

- [0%] Key resharing (new operator keys but same validator pub key) - implemented 0%
- [50%] Key resharing (new operator keys but same validator pub key) - implemented 0%

- [ ] CLI command and message to initiate resharing protocol
- [ ] Handlers of DKG key resharing messages exchange
- [x] CLI command and message to initiate resharing protocol
- [x] Handlers of DKG key resharing messages exchange
- [ ] Store new keys, update storage at operators
- [ ] Error handling
- [ ] Unit tests

---

- [0%] Private key recreation from shares at initiator - implemented 0%
- [ ] CLI command and message to initiate reconstruction of the key from shares
- [ ] Handlers to send encrypted with RSA pub key shares to initiator
- [ ] DKG private key recovery from shares
- [ ] Keystore storage of validator priv key
- [ ] Error handling
- [ ] Unit tests
7 changes: 0 additions & 7 deletions build_for_testing.sh

This file was deleted.

24 changes: 17 additions & 7 deletions cli/initiator/initiator.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import (
)

func init() {
flags.ThresholdFlag(StartDKG)
flags.WithdrawAddressFlag(StartDKG)
flags.OperatorsInfoFlag(StartDKG)
flags.OperatorIDsFlag(StartDKG)
Expand All @@ -30,7 +29,6 @@ func init() {
flags.ForkVersionFlag(StartDKG)
flags.AddDepositResultStorePathFlag(StartDKG)
flags.AddSSVPayloadResultStorePathFlag(StartDKG)
viper.BindPFlag("threshold", StartDKG.PersistentFlags().Lookup("threshold"))
viper.BindPFlag("withdrawAddress", StartDKG.PersistentFlags().Lookup("withdrawAddress"))
viper.BindPFlag("operatorIDs", StartDKG.PersistentFlags().Lookup("operatorIDs"))
viper.BindPFlag("operatorsInfoPath", StartDKG.PersistentFlags().Lookup("operatorsInfoPath"))
Expand Down Expand Up @@ -111,10 +109,6 @@ var StartDKG = &cobra.Command{
if withdrawAddr == "" {
logger.Fatal("failed to get withdrawal address flag value", zap.Error(err))
}
threshold := viper.GetUint64("threshold")
if threshold < 1 {
logger.Fatal("failed to get threshold flag value", zap.Error(err))
}
forkHex := viper.GetString("fork")
if forkHex == "" {
logger.Fatal("failed to get fork version flag value", zap.Error(err))
Expand Down Expand Up @@ -148,7 +142,7 @@ var StartDKG = &cobra.Command{
if err != nil {
logger.Fatal("failed to decode withdrawal public key", zap.Error(err))
}
depositData, keyShares, err := dkgClient.StartDKG(withdrawPubKey, parts, threshold, fork, forkName, common.HexToAddress(owner), nonce)
depositData, keyShares, err := dkgClient.StartDKG(withdrawPubKey, parts, fork, forkName, common.HexToAddress(owner), nonce)

if err != nil {
logger.Fatal("failed to initiate DKG ceremony", zap.Error(err))
Expand All @@ -167,6 +161,22 @@ var StartDKG = &cobra.Command{
}

logger.Info("DKG protocol finished successfull")
fmt.Println(`
▓█████▄ ██▓ ██████ ▄████▄ ██▓ ▄▄▄ ██▓ ███▄ ▄███▓▓█████ ██▀███
▒██▀ ██▌▓██▒▒██ ▒ ▒██▀ ▀█ ▓██▒ ▒████▄ ▓██▒▓██▒▀█▀ ██▒▓█ ▀ ▓██ ▒ ██▒
░██ █▌▒██▒░ ▓██▄ ▒▓█ ▄ ▒██░ ▒██ ▀█▄ ▒██▒▓██ ▓██░▒███ ▓██ ░▄█ ▒
░▓█▄ ▌░██░ ▒ ██▒▒▓▓▄ ▄██▒▒██░ ░██▄▄▄▄██ ░██░▒██ ▒██ ▒▓█ ▄ ▒██▀▀█▄
░▒████▓ ░██░▒██████▒▒▒ ▓███▀ ░░██████▒▓█ ▓██▒░██░▒██▒ ░██▒░▒████▒░██▓ ▒██▒
▒▒▓ ▒ ░▓ ▒ ▒▓▒ ▒ ░░ ░▒ ▒ ░░ ▒░▓ ░▒▒ ▓▒█░░▓ ░ ▒░ ░ ░░░ ▒░ ░░ ▒▓ ░▒▓░
░ ▒ ▒ ▒ ░░ ░▒ ░ ░ ░ ▒ ░ ░ ▒ ░ ▒ ▒▒ ░ ▒ ░░ ░ ░ ░ ░ ░ ░▒ ░ ▒░
░ ░ ░ ▒ ░░ ░ ░ ░ ░ ░ ░ ▒ ▒ ░░ ░ ░ ░░ ░
░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░
░ ░
This tool was not audited.
When using distributed key generation you understand all the risks involved with
experimental cryptography.
`)
},
}

Expand Down
3 changes: 1 addition & 2 deletions config/initiator.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
threshold: 3
operatorIDs: [1, 2, 3, 4]
withdrawAddress: "0x0000000000000000000000000000000000000009"
withdrawAddress: "0000000000000000000000000000000000000009"
owner: "0x81592c3de184a3e2c0dcb5a261bc107bfa91f494"
nonce: 4
fork: "00000000"
Expand Down
3 changes: 0 additions & 3 deletions config/operator.yaml

This file was deleted.

6 changes: 3 additions & 3 deletions integration/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ func TestHappyFlow(t *testing.T) {
clnt := client.New(ops)
withdraw := newEthAddress(t)
owner := newEthAddress(t)
depositData, ks, err := clnt.StartDKG(withdraw.Bytes(), []uint64{1, 2, 3, 101}, 3, [4]byte{0, 0, 0, 0}, "mainnnet", owner, 0)
depositData, ks, err := clnt.StartDKG(withdraw.Bytes(), []uint64{1, 2, 3, 101}, [4]byte{0, 0, 0, 0}, "mainnnet", owner, 0)
require.NoError(t, err)
sharesDataSigned, err := hex.DecodeString(ks.Payload.Readable.Shares[2:])
require.NoError(t, err)
Expand Down Expand Up @@ -106,7 +106,7 @@ func TestHappyFlow(t *testing.T) {
clnt := client.New(ops)
withdraw := newEthAddress(t)
owner := newEthAddress(t)
depositData, ks, err := clnt.StartDKG(withdraw.Bytes(), []uint64{1, 2, 3, 4, 5, 6, 7}, 6, [4]byte{0, 0, 0, 0}, "mainnnet", owner, 0)
depositData, ks, err := clnt.StartDKG(withdraw.Bytes(), []uint64{1, 2, 3, 4, 5, 6, 7}, [4]byte{0, 0, 0, 0}, "mainnnet", owner, 0)
require.NoError(t, err)
sharesDataSigned, err := hex.DecodeString(ks.Payload.Readable.Shares[2:])
require.NoError(t, err)
Expand Down Expand Up @@ -151,7 +151,7 @@ func TestHappyFlow(t *testing.T) {
clnt := client.New(ops)
withdraw := newEthAddress(t)
owner := newEthAddress(t)
depositData, ks, err := clnt.StartDKG(withdraw.Bytes(), []uint64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}, 9, [4]byte{0, 0, 0, 0}, "mainnnet", owner, 0)
depositData, ks, err := clnt.StartDKG(withdraw.Bytes(), []uint64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}, [4]byte{0, 0, 0, 0}, "mainnnet", owner, 0)
require.NoError(t, err)
sharesDataSigned, err := hex.DecodeString(ks.Payload.Readable.Shares[2:])
require.NoError(t, err)
Expand Down
30 changes: 25 additions & 5 deletions pkgs/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -353,11 +353,20 @@ func (c *Client) MakeMultiple(id [24]byte, allmsgs [][]byte) (*wire.MultipleSign
return final, nil
}

func (c *Client) StartDKG(withdraw []byte, ids []uint64, threshold uint64, fork [4]byte, forkName string, owner common.Address, nonce uint64) (*DepositDataJson, *KeyShares, error) {
// threshold cant be more than number of operators
if threshold == 0 || threshold > uint64(len(ids)) {
return nil, nil, fmt.Errorf("wrong threshold")
func (c *Client) StartDKG(withdraw []byte, ids []uint64, fork [4]byte, forkName string, owner common.Address, nonce uint64) (*DepositDataJson, *KeyShares, error) {
if len(ids) < 4 {
return nil, nil, fmt.Errorf("minimum supported amount of operators is 4")
}
// limit amount of operators
if len(ids) > 13 {
return nil, nil, fmt.Errorf("maximum supported amount of operators is 13")
}
// check that operator ids are unique
if err := c.validateOpIDs(ids); err != nil {
return nil, nil, err
}
// compute threshold (3f+1)
threshold := len(ids) - ((len(ids) - 1) / 3)
parts := make([]*wire.Operator, 0, 0)
for _, id := range ids {
op, ok := c.Operators[id]
Expand All @@ -383,7 +392,7 @@ func (c *Client) StartDKG(withdraw []byte, ids []uint64, threshold uint64, fork
// make init message
init := &wire.Init{
Operators: parts,
T: threshold,
T: uint64(threshold),
WithdrawalCredentials: withdraw,
Fork: fork,
Owner: owner,
Expand Down Expand Up @@ -860,3 +869,14 @@ func splitBytes(buf []byte, lim int) [][]byte {
}
return chunks
}

func (c *Client) validateOpIDs(ids []uint64) error {
opMap := make(map[uint64]bool)
for _, id := range ids {
if opMap[id] {
return fmt.Errorf("operators ids should be unique in the list")
}
opMap[id] = true
}
return nil
}
Loading

0 comments on commit f22c75f

Please sign in to comment.