Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(zetaclient): Revamp TSS package #3170

Merged
merged 40 commits into from
Nov 25, 2024
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
2e1570d
Streamline tss config
swift1337 Nov 15, 2024
969f17d
Revamp TSS keygen ceremony code
swift1337 Nov 15, 2024
cdd27a8
Refactor tss key sign test; streamline sig verification
swift1337 Nov 15, 2024
39db08a
Remove optional pub key (tss)
swift1337 Nov 18, 2024
e1109fd
Implement PubKey entity
swift1337 Nov 19, 2024
1190db9
Implement new TSS service layer. Add unit tests
swift1337 Nov 19, 2024
82778ed
Add prometheus metrics
swift1337 Nov 19, 2024
ae2479b
Restructure files
swift1337 Nov 19, 2024
62f9c26
Implement TSS Setup Flow
swift1337 Nov 20, 2024
e1c8bfd
Adapt some test cases; fix typo
swift1337 Nov 20, 2024
7f8cdf4
Merge pubkey.go with crypto.go
swift1337 Nov 20, 2024
63a7313
Add tss key pass alongside with hotkey pass
swift1337 Nov 20, 2024
ade21aa
Simplify start.go; drop old tss from zetaclient
swift1337 Nov 20, 2024
c5b10c6
Fix cyclic imports
swift1337 Nov 20, 2024
1657b1a
Revamp TSS mock. Fix unit tests across zetaclient
swift1337 Nov 21, 2024
4dbf3f8
Remove tss historical list from outbound evm cctx
swift1337 Nov 21, 2024
8c7058c
TSS healthcheck
swift1337 Nov 21, 2024
f977b22
Fix flaky case when GetTSS() returns an empty string
swift1337 Nov 21, 2024
034f19f
Update changelog
swift1337 Nov 21, 2024
f7af651
Merge remote-tracking branch 'origin/develop' into feat/zetaclient/ts…
swift1337 Nov 21, 2024
097400d
Fix typos
swift1337 Nov 21, 2024
d6cc841
Forward docker pprof in e2e
swift1337 Nov 21, 2024
b56bf33
Forward pprof in e2e docker
swift1337 Nov 21, 2024
a9229ae
Simplify VerifySignature
swift1337 Nov 21, 2024
8eba16d
Fix typo
swift1337 Nov 21, 2024
f88aca1
Fix batch signature verification
swift1337 Nov 21, 2024
18b7acc
Add readme
swift1337 Nov 21, 2024
b5c367a
Fix typo
swift1337 Nov 21, 2024
3a856c2
Fix typos in healthcheck
swift1337 Nov 22, 2024
1471f12
Address PR comments [1]
swift1337 Nov 22, 2024
44d75a4
Address PR comments [2]
swift1337 Nov 22, 2024
21bc93e
Address PR comments [3] (improve errors clarity)
swift1337 Nov 22, 2024
6036675
Merge branch 'develop' into feat/zetaclient/tss-improvements
swift1337 Nov 22, 2024
e3f43d8
Address PR comments [4]
swift1337 Nov 22, 2024
578bf5e
Remove redundant methods from app context
swift1337 Nov 22, 2024
9b264d9
Address PR comments [5]
swift1337 Nov 22, 2024
14c2de2
Address PR comments [6] (test coverage)
swift1337 Nov 22, 2024
7d9c96b
Fix TSS migration test
swift1337 Nov 22, 2024
95c87d8
Merge branch 'develop' into feat/zetaclient/tss-improvements
swift1337 Nov 22, 2024
2c02cc2
Fix btc issue
swift1337 Nov 22, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 17 additions & 54 deletions cmd/zetaclientd/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@

import (
"context"
"encoding/json"
"fmt"
"io"
"os"
"os/signal"
"path/filepath"
Expand All @@ -13,11 +11,9 @@
"syscall"
"time"

ecdsakeygen "github.com/bnb-chain/tss-lib/ecdsa/keygen"
"github.com/cometbft/cometbft/crypto/secp256k1"
"github.com/libp2p/go-libp2p/core/peer"
"github.com/libp2p/go-libp2p/p2p/protocol/ping"
maddr "github.com/multiformats/go-multiaddr"
"github.com/pkg/errors"
"github.com/rs/zerolog/log"
"github.com/spf13/cobra"
Expand All @@ -35,15 +31,13 @@
"github.com/zeta-chain/node/zetaclient/maintenance"
"github.com/zeta-chain/node/zetaclient/metrics"
"github.com/zeta-chain/node/zetaclient/orchestrator"
mc "github.com/zeta-chain/node/zetaclient/tss"

Check failure on line 34 in cmd/zetaclientd/start.go

View workflow job for this annotation

GitHub Actions / lint

could not import github.com/zeta-chain/node/zetaclient/tss (-: # github.com/zeta-chain/node/zetaclient/tss
"github.com/zeta-chain/node/zetaclient/zetacore"
)

// todo revamp
// Start starts zetaclientd process todo revamp
// https://github.com/zeta-chain/node/issues/3119
// https://github.com/zeta-chain/node/issues/3112
var preParams *ecdsakeygen.LocalPreParams

func Start(_ *cobra.Command, _ []string) error {
// Prompt for Hotkey, TSS key-share and relayer key passwords
titles := []string{"HotKey", "TSS", "Solana Relayer Key"}
Expand Down Expand Up @@ -164,12 +158,16 @@
}
priKey := secp256k1.PrivKey(hotkeyPk.Bytes()[:32])

// Generate pre Params if not present already
peers, err := initPeers(cfg.Peer)
tssBootstrapPeers, err := mc.MultiAddressFromString(cfg.Peer)
if err != nil {
log.Error().Err(err).Msg("peer address error")
// this is okay, we still have whitelisted peers to connect to
startLogger.Warn().Err(err).Msg("TSS bootstrap peers error")
}

tssPreParams, err := mc.ResolvePreParamsFromPath(cfg.PreParamsPath)
if err != nil {
return errors.Wrap(err, "unable to resolve TSS pre params. Use `zetaclient tss gen-pre-params`")
}
initPreParams(cfg.PreParamsPath)

m, err := metrics.NewMetrics()
if err != nil {
Expand Down Expand Up @@ -201,9 +199,9 @@

// Create TSS server
tssServer, err := mc.SetupTSSServer(
peers,
tssBootstrapPeers,
priKey,
preParams,
tssPreParams,
appContext.Config(),
tssKeyPass,
true,
Expand Down Expand Up @@ -256,9 +254,9 @@

// Generate a new TSS if keygen is set and add it into the tss server
// If TSS has already been generated, and keygen was successful ; we use the existing TSS
err = mc.Generate(ctx, zetacoreClient, tssServer, masterLogger)
err = mc.KeygenCeremony(ctx, tssServer, zetacoreClient, masterLogger)
if err != nil {
return err
return errors.Wrap(err, "unable to run tss keygen ceremony")
}

tss, err := mc.New(
Expand All @@ -273,9 +271,10 @@
return err
}
if cfg.TestTssKeysign {
err = mc.TestTSS(tss.CurrentPubkey, *tss.Server, masterLogger)
if err != nil {
startLogger.Error().Err(err).Msgf("TestTSS error : %s", tss.CurrentPubkey)
if err = mc.TestKeySign(tss.Server, tss.CurrentPubkey, startLogger); err != nil {
startLogger.Error().Err(err).
Str("tss.public_key", tss.CurrentPubkey).
Msg("TSS key-sign failed")
}
}

Expand Down Expand Up @@ -409,42 +408,6 @@
return nil
}

func initPeers(peer string) ([]maddr.Multiaddr, error) {
var peers []maddr.Multiaddr

if peer != "" {
address, err := maddr.NewMultiaddr(peer)
if err != nil {
log.Error().Err(err).Msg("NewMultiaddr error")
return []maddr.Multiaddr{}, err
}
peers = append(peers, address)
}
return peers, nil
}

func initPreParams(path string) {
if path != "" {
path = filepath.Clean(path)
log.Info().Msgf("pre-params file path %s", path)
preParamsFile, err := os.Open(path)
if err != nil {
log.Error().Err(err).Msg("open pre-params file failed; skip")
} else {
bz, err := io.ReadAll(preParamsFile)
if err != nil {
log.Error().Err(err).Msg("read pre-params file failed; skip")
} else {
err = json.Unmarshal(bz, &preParams)
if err != nil {
log.Error().Err(err).Msg("unmarshal pre-params file failed; skip and generate new one")
preParams = nil // skip reading pre-params; generate new one instead
}
}
}
}
}

// isObserverNode checks whether THIS node is an observer node.
func isObserverNode(ctx context.Context, client *zetacore.Client) (bool, error) {
observers, err := client.GetObserverList(ctx)
Expand Down
2 changes: 1 addition & 1 deletion zetaclient/chains/bitcoin/signer/signer_keysign_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ func getTSSTX(
return "", err
}

sig65B, err := tss.Sign(ctx, witnessHash, 10, 10, 0, "")
sig65B, err := tss.Sign(ctx, witnessHash, 10, 10, 0)
R := &btcec.ModNScalar{}
R.SetBytes((*[32]byte)(sig65B[:32]))
S := &btcec.ModNScalar{}
Expand Down
2 changes: 1 addition & 1 deletion zetaclient/chains/evm/signer/signer.go
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ func (signer *Signer) Sign(

hashBytes := signer.ethSigner.Hash(tx).Bytes()

sig, err := signer.TSS().Sign(ctx, hashBytes, height, nonce, signer.Chain().ChainId, "")
sig, err := signer.TSS().Sign(ctx, hashBytes, height, nonce, signer.Chain().ChainId)
if err != nil {
return nil, nil, nil, err
}
Expand Down
20 changes: 3 additions & 17 deletions zetaclient/chains/interfaces/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -227,25 +227,11 @@ type EVMJSONRPCClient interface {
// TSSSigner is the interface for TSS signer
type TSSSigner interface {
Pubkey() []byte

// Sign signs the data
// Note: it specifies optionalPubkey to use a different pubkey than the current pubkey set during keygen
// TODO: check if optionalPubkey is needed
// https://github.com/zeta-chain/node/issues/2085
Sign(
ctx context.Context,
data []byte,
height uint64,
nonce uint64,
chainID int64,
optionalPubkey string,
) ([65]byte, error)

// SignBatch signs the data in batch
SignBatch(ctx context.Context, digests [][]byte, height uint64, nonce uint64, chainID int64) ([][65]byte, error)

EVMAddress() ethcommon.Address
EVMAddressList() []ethcommon.Address
BTCAddress(chainID int64) (*btcutil.AddressWitnessPubKeyHash, error)
PubKeyCompressedBytes() []byte

Sign(ctx context.Context, data []byte, height, nonce uint64, chainID int64) ([65]byte, error)
SignBatch(ctx context.Context, digests [][]byte, height, nonce uint64, chainID int64) ([][65]byte, error)
}
2 changes: 1 addition & 1 deletion zetaclient/chains/solana/signer/whitelist.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ func (signer *Signer) createAndSignMsgWhitelist(

// sign the message with TSS to get an ECDSA signature.
// the produced signature is in the [R || S || V] format where V is 0 or 1.
signature, err := signer.TSS().Sign(ctx, msgHash[:], height, nonce, chain.ChainId, "")
signature, err := signer.TSS().Sign(ctx, msgHash[:], height, nonce, chain.ChainId)
if err != nil {
return nil, errors.Wrap(err, "Key-sign failed")
}
Expand Down
2 changes: 1 addition & 1 deletion zetaclient/chains/solana/signer/withdraw.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ func (signer *Signer) createAndSignMsgWithdraw(

// sign the message with TSS to get an ECDSA signature.
// the produced signature is in the [R || S || V] format where V is 0 or 1.
signature, err := signer.TSS().Sign(ctx, msgHash[:], height, nonce, chain.ChainId, "")
signature, err := signer.TSS().Sign(ctx, msgHash[:], height, nonce, chain.ChainId)
if err != nil {
return nil, errors.Wrap(err, "Key-sign failed")
}
Expand Down
2 changes: 1 addition & 1 deletion zetaclient/chains/solana/signer/withdraw_spl.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ func (signer *Signer) createAndSignMsgWithdrawSPL(

// sign the message with TSS to get an ECDSA signature.
// the produced signature is in the [R || S || V] format where V is 0 or 1.
signature, err := signer.TSS().Sign(ctx, msgHash[:], height, nonce, chain.ChainId, "")
signature, err := signer.TSS().Sign(ctx, msgHash[:], height, nonce, chain.ChainId)
if err != nil {
return nil, errors.Wrap(err, "Key-sign failed")
}
Expand Down
2 changes: 1 addition & 1 deletion zetaclient/chains/ton/observer/observer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ func (ts *testSuite) sign(msg signable) {
hash, err := msg.Hash()
require.NoError(ts.t, err)

sig, err := ts.tss.Sign(ts.ctx, hash[:], 0, 0, 0, "")
sig, err := ts.tss.Sign(ts.ctx, hash[:], 0, 0, 0)
require.NoError(ts.t, err)

msg.SetSignature(sig)
Expand Down
2 changes: 1 addition & 1 deletion zetaclient/chains/ton/signer/signer.go
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ func (s *Signer) SignMessage(ctx context.Context, msg Signable, zetaHeight, nonc
chainID := s.Chain().ChainId

// sig = [65]byte {R, S, V (recovery ID)}
sig, err := s.TSS().Sign(ctx, hash[:], zetaHeight, nonce, chainID, "")
sig, err := s.TSS().Sign(ctx, hash[:], zetaHeight, nonce, chainID)
if err != nil {
return errors.Wrap(err, "unable to sign the message")
}
Expand Down
2 changes: 1 addition & 1 deletion zetaclient/chains/ton/signer/signer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ func (ts *testSuite) Sign(msg Signable) {
hash, err := msg.Hash()
require.NoError(ts.t, err)

sig, err := ts.tss.Sign(ts.ctx, hash[:], 0, 0, 0, "")
sig, err := ts.tss.Sign(ts.ctx, hash[:], 0, 0, 0)
require.NoError(ts.t, err)

msg.SetSignature(sig)
Expand Down
2 changes: 1 addition & 1 deletion zetaclient/testutils/mocks/tss_signer.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ func (s *TSS) WithPrivKey(privKey *ecdsa.PrivateKey) *TSS {
}

// Sign uses test key unrelated to any tss key in production
func (s *TSS) Sign(_ context.Context, data []byte, _ uint64, _ uint64, _ int64, _ string) ([65]byte, error) {
func (s *TSS) Sign(_ context.Context, data []byte, _ uint64, _ uint64, _ int64) ([65]byte, error) {
// return error if tss is paused
if s.paused {
return [65]byte{}, fmt.Errorf("tss is paused")
Expand Down
55 changes: 55 additions & 0 deletions zetaclient/tss/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package tss

import (
"encoding/json"
"os"
"path/filepath"

"github.com/bnb-chain/tss-lib/ecdsa/keygen"
"github.com/multiformats/go-multiaddr"
"github.com/pkg/errors"
tsscommon "gitlab.com/thorchain/tss/go-tss/common"
)

const (
Port = 6668
Version = "0.14.0"
Algo = tsscommon.ECDSA
)

// MultiAddressFromString parses a string into a slice of addresses (for convenience).
func MultiAddressFromString(peer string) ([]multiaddr.Multiaddr, error) {
if peer == "" {
return nil, errors.New("peer is empty")
}

ma, err := multiaddr.NewMultiaddr(peer)
if err != nil {
return nil, err
}

return []multiaddr.Multiaddr{ma}, nil
}

// ResolvePreParamsFromPath resolves TSS pre-params from json config by path.
// Error indicates that the pre-params file is not found or invalid.
// FYI: pre-params are generated by keygen.GeneratePreParams.
func ResolvePreParamsFromPath(path string) (*keygen.LocalPreParams, error) {
if path == "" {
return nil, errors.New("pre-params path is empty")
}

path = filepath.Clean(path)

raw, err := os.ReadFile(path)
if err != nil {
return nil, errors.Wrapf(err, "unable to read pre-params at %q", path)
}

var pp keygen.LocalPreParams
if err = json.Unmarshal(raw, &pp); err != nil {
return nil, errors.Wrapf(err, "unable to decode pre-params at %q", path)
}

return &pp, nil
}
80 changes: 80 additions & 0 deletions zetaclient/tss/crypto.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package tss

import (
"bytes"
"encoding/base64"
"strings"

"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/ethereum/go-ethereum/crypto"
"github.com/pkg/errors"
"gitlab.com/thorchain/tss/go-tss/keysign"

"github.com/zeta-chain/node/pkg/cosmos"
)

var (
base64Decode = base64.StdEncoding.Decode
base64DecodeString = base64.StdEncoding.DecodeString
base64EncodeString = base64.StdEncoding.EncodeToString
)

// VerifySignature checks that keysign.Signature is valid and origins from expected TSS public key.
// Also returns signature as [65]byte (R, S, V)
func VerifySignature(sig keysign.Signature, tssPubKey string, expectedMsgHash []byte) ([65]byte, error) {
// Check that msg hash equals msg hash in the signature
actualMsgHash, err := base64DecodeString(sig.Msg)
switch {
case err != nil:
return [65]byte{}, errors.Wrap(err, "unable to decode message hash")
case !bytes.Equal(expectedMsgHash, actualMsgHash):
return [65]byte{}, errors.New("message hash mismatch")
}

// Prepare expected public key
expectedPubKey, err := cosmos.GetPubKeyFromBech32(cosmos.Bech32PubKeyTypeAccPub, tssPubKey)
if err != nil {
return [65]byte{}, errors.Wrap(err, "unable to decode tss pub key from bech32")
}

sigBytes, err := SignatureToBytes(sig)
if err != nil {
return [65]byte{}, errors.Wrap(err, "unable to convert signature to bytes")
}

// Recover public key from signature
actualPubKey, err := crypto.SigToPub(expectedMsgHash, sigBytes[:])
if err != nil {
return [65]byte{}, errors.Wrap(err, "unable to recover public key from signature")
}

if !bytes.Equal(expectedPubKey.Bytes(), crypto.CompressPubkey(actualPubKey)) {
return [65]byte{}, errors.New("public key mismatch")
}

return sigBytes, nil
}

// SignatureToBytes converts keysign.Signature to [65]byte (R, S, V)
func SignatureToBytes(input keysign.Signature) (sig [65]byte, err error) {
if _, err = base64Decode(sig[:32], []byte(input.R)); err != nil {
return sig, errors.Wrap(err, "unable to decode R")
}

if _, err = base64Decode(sig[32:64], []byte(input.S)); err != nil {
return sig, errors.Wrap(err, "unable to decode S")
}

if _, err = base64Decode(sig[64:65], []byte(input.RecoveryID)); err != nil {
return sig, errors.Wrap(err, "unable to decode RecoveryID (V)")
}

return sig, nil
}

// combineDigests combines the digests
func combineDigests(digestList []string) []byte {

Check failure on line 76 in zetaclient/tss/crypto.go

View workflow job for this annotation

GitHub Actions / build-and-test

other declaration of combineDigests

Check failure on line 76 in zetaclient/tss/crypto.go

View workflow job for this annotation

GitHub Actions / build-and-test

other declaration of combineDigests

Check failure on line 76 in zetaclient/tss/crypto.go

View workflow job for this annotation

GitHub Actions / lint

other declaration of combineDigests (typecheck)

Check failure on line 76 in zetaclient/tss/crypto.go

View workflow job for this annotation

GitHub Actions / lint

other declaration of combineDigests) (typecheck)
digestConcat := strings.Join(digestList, "")
digestBytes := chainhash.DoubleHashH([]byte(digestConcat))
return digestBytes.CloneBytes()
}
Loading
Loading