Skip to content

Commit

Permalink
dkg: add frost command test (#482)
Browse files Browse the repository at this point in the history
Adds frost algo to the dkg command test.
- Add public shares to cluster lock file.

category: test
ticket: #478 
feature_set: alpha
  • Loading branch information
corverroos committed May 5, 2022
1 parent 44ce408 commit c815250
Show file tree
Hide file tree
Showing 8 changed files with 135 additions and 45 deletions.
9 changes: 7 additions & 2 deletions cluster/distvalidator.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,13 @@ type DistValidator struct {
// PubKey is the root distributed public key.
PubKey string `json:"distributed_public_key"`

// Verifiers are the public shares.
Verifiers [][]byte `json:"threshold_verifiers"`
// PubShares are the public keys corresponding to each node's secret key share.
// It can be used to verify a partial signature created by any node in the cluster.
PubShares [][]byte `json:"public_shares,omitempty"`

// Verifiers are the threshold verifier commitments.
// Deprecated: Use PubShares.
Verifiers [][]byte `json:"threshold_verifiers,omitempty"`

// FeeRecipientAddress Ethereum address override for this validator, defaults to definition withdrawal address.
FeeRecipientAddress string `json:"fee_recipient_address,omitempty"`
Expand Down
2 changes: 1 addition & 1 deletion dkg/disk.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ func loadDefinition(conf Config) (cluster.Definition, error) {
func writeKeystores(datadir string, shares []share) error {
var secrets []*bls_sig.SecretKey
for _, s := range shares {
secret, err := tblsconv.ShareToSecret(s.Share)
secret, err := tblsconv.ShareToSecret(s.SecretShare)
if err != nil {
return err
}
Expand Down
1 change: 1 addition & 0 deletions dkg/dkg.go
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ func dvsFromShares(shares []share) ([]cluster.DistValidator, error) {
dvs = append(dvs, cluster.DistValidator{
PubKey: fmt.Sprintf("%#x", msg.PubKey),
Verifiers: msg.Verifiers,
PubShares: msg.PubShares,
})
}

Expand Down
65 changes: 48 additions & 17 deletions dkg/dkg_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package dkg_test

import (
"context"
"crypto/ecdsa"
"encoding/json"
"fmt"
"net/http"
Expand All @@ -36,25 +37,40 @@ import (
"github.com/obolnetwork/charon/dkg"
"github.com/obolnetwork/charon/p2p"
"github.com/obolnetwork/charon/tbls"
"github.com/obolnetwork/charon/tbls/tblsconv"
"github.com/obolnetwork/charon/testutil"
"github.com/obolnetwork/charon/testutil/keystore"
)

func TestDKG(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

const (
nodes = 3
vals = 2
)

t.Run("keycast", func(t *testing.T) {
lock, keys, _ := cluster.NewForT(t, vals, nodes, nodes, 0)
lock.Definition.DKGAlgorithm = "keycast"
testDKG(t, lock.Definition, keys)
})

t.Run("frost", func(t *testing.T) {
lock, keys, _ := cluster.NewForT(t, vals, nodes, nodes, 0)
lock.Definition.DKGAlgorithm = "frost"
testDKG(t, lock.Definition, keys)
})
}

func testDKG(t *testing.T, def cluster.Definition, p2pKeys []*ecdsa.PrivateKey) {
t.Helper()

ctx, cancel := context.WithCancel(context.Background())
defer cancel()

// Start bootnode
bootnode, errChan := startBootnode(ctx, t)

// Setup
lock, keys, _ := cluster.NewForT(t, vals, 3, 3, 0)

dir, err := os.MkdirTemp("", "")
require.NoError(t, err)

Expand All @@ -64,19 +80,19 @@ func TestDKG(t *testing.T) {
UDPBootnodes: []string{bootnode},
},
Log: log.DefaultConfig(),
TestDef: &lock.Definition,
TestDef: &def,
}

// Run dkg for each node
var eg errgroup.Group
for i := 0; i < nodes; i++ {
for i := 0; i < len(def.Operators); i++ {
conf := conf
conf.DataDir = path.Join(dir, fmt.Sprintf("node%d", i))
conf.P2P.TCPAddrs = []string{testutil.AvailableAddr(t).String()}
conf.P2P.UDPAddr = testutil.AvailableAddr(t).String()

require.NoError(t, os.MkdirAll(conf.DataDir, 0o755))
err := crypto.SaveECDSA(p2p.KeyPath(conf.DataDir), keys[i])
err := crypto.SaveECDSA(p2p.KeyPath(conf.DataDir), p2pKeys[i])
require.NoError(t, err)

eg.Go(func() error {
Expand All @@ -97,9 +113,10 @@ func TestDKG(t *testing.T) {

select {
case err := <-errChan:
// If this returns first, something went wrong with the bootnode and the test will fail.
cancel()
testutil.SkipIfBindErr(t, err)
require.NoError(t, err)
require.Fail(t, "bootnode error: %v", err)
case err := <-runChan:
cancel()
testutil.SkipIfBindErr(t, err)
Expand All @@ -108,17 +125,17 @@ func TestDKG(t *testing.T) {

// Read generated lock and keystores from disk
var (
shares = make([][]*bls_sig.SecretKey, vals)
locks []cluster.Lock
secretShares = make([][]*bls_sig.SecretKey, def.NumValidators)
locks []cluster.Lock
)
for i := 0; i < nodes; i++ {
for i := 0; i < len(def.Operators); i++ {
dataDir := path.Join(dir, fmt.Sprintf("node%d", i))
keyShares, err := keystore.LoadKeys(dataDir)
require.NoError(t, err)
require.Len(t, keyShares, vals)
require.Len(t, keyShares, def.NumValidators)

for i, key := range keyShares {
shares[i] = append(shares[i], key)
secretShares[i] = append(secretShares[i], key)
}

lockFile, err := os.ReadFile(path.Join(dataDir, "cluster_lock.json"))
Expand All @@ -142,15 +159,29 @@ func TestDKG(t *testing.T) {
}

// Ensure keystores can generate valid tbls aggregate signature.
for i := 0; i < vals; i++ {
for i := 0; i < def.NumValidators; i++ {
var sigs []*bls_sig.PartialSignature
for j := 0; j < nodes; j++ {
sig, err := tbls.Sign(shares[i][j], []byte("data"))
for j := 0; j < len(def.Operators); j++ {
msg := []byte("data")
sig, err := tbls.Sign(secretShares[i][j], msg)
require.NoError(t, err)
sigs = append(sigs, &bls_sig.PartialSignature{
Identifier: byte(j),
Signature: sig.Value,
})

// Ensure all public shares can verify the partial signature
for _, lock := range locks {
if len(lock.Validators[i].PubShares) == 0 {
// TODO(corver): convert keycast to use public shares, not verifiers.
continue
}
pk, err := tblsconv.KeyFromBytes(lock.Validators[i].PubShares[j])
require.NoError(t, err)
ok, err := tbls.Verify(pk, msg, sig)
require.NoError(t, err)
require.True(t, ok)
}
}
_, err := tbls.Aggregate(sigs)
require.NoError(t, err)
Expand Down
16 changes: 13 additions & 3 deletions dkg/frost.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package dkg

import (
"context"
"sort"

"github.com/coinbase/kryptology/pkg/core/curves"
"github.com/coinbase/kryptology/pkg/dkg/frost"
Expand Down Expand Up @@ -228,9 +229,18 @@ func makeShares(
m[key.SourceID] = pubShare
}

// Sort shares by vIdx
var vIdxs []int
for vIdx := range validators {
vIdxs = append(vIdxs, int(vIdx))
}
sort.Ints(vIdxs)

// Construct DKG result shares.
var shares []share
for vIdx, v := range validators {
for _, vIdx := range vIdxs {
v := validators[uint32(vIdx)]

pubkey, err := pointToPubKey(v.VerificationKey)
if err != nil {
return nil, err
Expand All @@ -243,8 +253,8 @@ func makeShares(

shares = append(shares, share{
PubKey: pubkey,
Share: secretShare,
PublicShares: pubShares[vIdx],
SecretShare: secretShare,
PublicShares: pubShares[uint32(vIdx)],
})
}

Expand Down
72 changes: 52 additions & 20 deletions dkg/keycast.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"context"
"encoding/json"
"io"
"sort"

"github.com/coinbase/kryptology/pkg/core/curves"
"github.com/coinbase/kryptology/pkg/sharing"
Expand All @@ -42,8 +43,8 @@ type kcTransport interface {
// share is the co-validator public key, tbls verifiers or tbls publis shares, and private key share.
// Each node in the cluster will receive one for each distributed validator.
type share struct {
PubKey *bls_sig.PublicKey
Share *bls_sig.SecretKeyShare
PubKey *bls_sig.PublicKey
SecretShare *bls_sig.SecretKeyShare

// One of the two below will be populated,
Verifier *sharing.FeldmanVerifier
Expand All @@ -52,9 +53,10 @@ type share struct {

// shareMsg is the share message wire format sent by the dealer.
type shareMsg struct {
PubKey []byte
Verifiers [][]byte
Share []byte
PubKey []byte
PubShares [][]byte
Verifiers [][]byte
SecretShare []byte
}

func runKeyCast(ctx context.Context, def cluster.Definition, tx kcTransport, nodeIdx int, random io.Reader) ([]share, error) {
Expand Down Expand Up @@ -183,9 +185,9 @@ func createShares(numValidators, numNodes, threshold int, random io.Reader) ([][

for ni := 0; ni < numNodes; ni++ {
resp[ni] = append(resp[ni], share{
PubKey: pubkey,
Verifier: verifier,
Share: shares[ni],
PubKey: pubkey,
Verifier: verifier,
SecretShare: shares[ni],
})
}
}
Expand All @@ -195,25 +197,44 @@ func createShares(numValidators, numNodes, threshold int, random io.Reader) ([][

// msgFromShare returns a new share message to send over the wire.
func msgFromShare(s share) (shareMsg, error) {
pk, err := s.PubKey.MarshalBinary()
pubkey, err := s.PubKey.MarshalBinary()
if err != nil {
return shareMsg{}, errors.Wrap(err, "marshal pubkey")
}

var verifiers [][]byte
for _, commitment := range s.Verifier.Commitments {
verifiers = append(verifiers, commitment.ToAffineCompressed())
if s.Verifier != nil {
for _, commitment := range s.Verifier.Commitments {
verifiers = append(verifiers, commitment.ToAffineCompressed())
}
}

// Sort pub shares by id/index.
var pubSharesIDs []int
for id := range s.PublicShares {
pubSharesIDs = append(pubSharesIDs, int(id))
}
sort.Ints(pubSharesIDs)

b, err := s.Share.MarshalBinary()
var pubShares [][]byte
for _, id := range pubSharesIDs {
b, err := s.PublicShares[uint32(id)].MarshalBinary()
if err != nil {
return shareMsg{}, errors.Wrap(err, "marshal public share")
}
pubShares = append(pubShares, b)
}

secretShare, err := s.SecretShare.MarshalBinary()
if err != nil {
return shareMsg{}, errors.Wrap(err, "marshal share")
return shareMsg{}, errors.Wrap(err, "marshal secretShare share")
}

return shareMsg{
PubKey: pk,
Verifiers: verifiers,
Share: b,
PubKey: pubkey,
Verifiers: verifiers,
SecretShare: secretShare,
PubShares: pubShares,
}, nil
}

Expand All @@ -234,14 +255,25 @@ func shareFromMsg(msg shareMsg) (share, error) {
commitments = append(commitments, c)
}

pubShares := make(map[uint32]*bls_sig.PublicKey)
for id, bytes := range msg.PubShares {
pubShare := new(bls_sig.PublicKey)
if err := pubShare.UnmarshalBinary(bytes); err != nil {
return share{}, errors.Wrap(err, "unmarshal public share")
}

pubShares[uint32(id+1)] = pubShare // Public shares IDs are 1-indexed.
}

secretShare := new(bls_sig.SecretKeyShare)
if err := secretShare.UnmarshalBinary(msg.Share); err != nil {
if err := secretShare.UnmarshalBinary(msg.SecretShare); err != nil {
return share{}, errors.Wrap(err, "unmarshal pubkey")
}

return share{
PubKey: pubKey,
Verifier: &sharing.FeldmanVerifier{Commitments: commitments},
Share: secretShare,
PubKey: pubKey,
Verifier: &sharing.FeldmanVerifier{Commitments: commitments},
SecretShare: secretShare,
PublicShares: pubShares,
}, nil
}
10 changes: 10 additions & 0 deletions tbls/tblsconv/tblsconv.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,16 @@ import (
"github.com/obolnetwork/charon/core"
)

// KeyFromBytes unmarshals the bytes into a kryptology bls public key.
func KeyFromBytes(bytes []byte) (*bls_sig.PublicKey, error) {
resp := new(bls_sig.PublicKey)
if err := resp.UnmarshalBinary(bytes); err != nil {
return nil, errors.Wrap(err, "unmarshal pubkey")
}

return resp, nil
}

// KeyFromETH2 converts an eth2 phase0 public key into a kryptology bls public key.
func KeyFromETH2(key eth2p0.BLSPubKey) (*bls_sig.PublicKey, error) {
resp := new(bls_sig.PublicKey)
Expand Down
5 changes: 3 additions & 2 deletions tbls/tss.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ func (t TSS) Threshold() int {

// PublicShare returns a share's public key by share index (identifier).
func (t TSS) PublicShare(shareIdx int) (*bls_sig.PublicKey, error) {
return getPubShare(shareIdx, t.verifier)
return GetPubShare(shareIdx, t.verifier)
}

func NewTSS(verifier *share.FeldmanVerifier, numShares int) (TSS, error) {
Expand Down Expand Up @@ -242,7 +242,8 @@ func SplitSecret(secret *bls_sig.SecretKey, t, n int, reader io.Reader) ([]*bls_
return sks, verifier, nil
}

func getPubShare(identifier int, verifier *share.FeldmanVerifier) (*bls_sig.PublicKey, error) {
// GetPubShare returns the public key share for the i'th/identifier/shareIdx share from the verifier commitments.
func GetPubShare(identifier int, verifier *share.FeldmanVerifier) (*bls_sig.PublicKey, error) {
curve := curves.GetCurveByName(verifier.Commitments[0].CurveName())
if curve != curves.BLS12381G1() {
return nil, errors.New("curve mismatch")
Expand Down

0 comments on commit c815250

Please sign in to comment.