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

chore: adds unsafeSignEOTS for testing double signing #193

Merged
merged 5 commits into from
Dec 5, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
* [#182](https://github.com/babylonlabs-io/finality-provider/pull/182) Remove fp manager
* [#184](https://github.com/babylonlabs-io/finality-provider/pull/184) eots manager sign record store
* [#190](https://github.com/babylonlabs-io/finality-provider/pull/190) Benchmark pub rand
* [#193](https://github.com/babylonlabs-io/finality-provider/pull/193) adds unsafeSignEOTS for e2e tests

### Bug Fixes

Expand Down
19 changes: 19 additions & 0 deletions eotsmanager/client/rpcclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,25 @@ func (c *EOTSManagerGRpcClient) SignEOTS(uid, chaiID, msg []byte, height uint64,
return &s, nil
}

func (c *EOTSManagerGRpcClient) UnsafeSignEOTS(uid, chaiID, msg []byte, height uint64, passphrase string) (*btcec.ModNScalar, error) {
req := &proto.SignEOTSRequest{
Uid: uid,
ChainId: chaiID,
Msg: msg,
Height: height,
Passphrase: passphrase,
}
res, err := c.client.UnsafeSignEOTS(context.Background(), req)
if err != nil {
return nil, err
}

var s btcec.ModNScalar
s.SetByteSlice(res.Sig)

return &s, nil
}

func (c *EOTSManagerGRpcClient) SignSchnorrSig(uid, msg []byte, passphrase string) (*schnorr.Signature, error) {
req := &proto.SignSchnorrSigRequest{Uid: uid, Msg: msg, Passphrase: passphrase}
res, err := c.client.SignSchnorrSig(context.Background(), req)
Expand Down
3 changes: 3 additions & 0 deletions eotsmanager/eotsmanager.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ type EOTSManager interface {
// or passPhrase is incorrect
SignEOTS(uid []byte, chainID []byte, msg []byte, height uint64, passphrase string) (*btcec.ModNScalar, error)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can mention that SignEOTS should have built-in anti-slashing mechanism to ensure signature for the same height will not be signed twice


// UnsafeSignEOTS should only be used in e2e tests for demonstration purposes. Use SignEOTS for real operations
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should mention that UnsafeSignEOTS does not check double-sign

UnsafeSignEOTS(uid []byte, chainID []byte, msg []byte, height uint64, passphrase string) (*btcec.ModNScalar, error)

// SignSchnorrSig signs a Schnorr signature using the private key of the finality provider
// It fails if the finality provider does not exist or the message size is not 32 bytes
// or passPhrase is incorrect
Expand Down
19 changes: 19 additions & 0 deletions eotsmanager/localmanager.go
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,25 @@ func (lm *LocalEOTSManager) SignEOTS(fpPk []byte, chainID []byte, msg []byte, he
return signedBytes, nil
}

// UnsafeSignEOTS should only be used in e2e test to demonstrate double sign
func (lm *LocalEOTSManager) UnsafeSignEOTS(fpPk []byte, chainID []byte, msg []byte, height uint64, passphrase string) (*btcec.ModNScalar, error) {
privRand, _, err := lm.getRandomnessPair(fpPk, chainID, height, passphrase)
if err != nil {
return nil, fmt.Errorf("failed to get private randomness: %w", err)
}

privKey, err := lm.getEOTSPrivKey(fpPk, passphrase)
if err != nil {
return nil, fmt.Errorf("failed to get EOTS private key: %w", err)
}

// Update metrics
lm.metrics.IncrementEotsFpTotalEotsSignCounter(hex.EncodeToString(fpPk))
lm.metrics.SetEotsFpLastEotsSignHeight(hex.EncodeToString(fpPk), float64(height))

return eots.Sign(privKey, privRand, msg)
}

func (lm *LocalEOTSManager) SignSchnorrSig(fpPk []byte, msg []byte, passphrase string) (*schnorr.Signature, error) {
privKey, err := lm.getEOTSPrivKey(fpPk, passphrase)
if err != nil {
Expand Down
46 changes: 26 additions & 20 deletions eotsmanager/proto/eotsmanager.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions eotsmanager/proto/eotsmanager.proto
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ service EOTSManager {
rpc SignEOTS (SignEOTSRequest)
returns (SignEOTSResponse);

// UnsafeSignEOTS used only for testing purpose. Use SignEOTS for real operations
rpc UnsafeSignEOTS (SignEOTSRequest)
returns (SignEOTSResponse);

// SignSchnorrSig signs a Schnorr sig with the EOTS private key
rpc SignSchnorrSig (SignSchnorrSigRequest)
returns (SignSchnorrSigResponse);
Expand Down
39 changes: 39 additions & 0 deletions eotsmanager/proto/eotsmanager_grpc.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 13 additions & 0 deletions eotsmanager/service/rpcserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,19 @@ func (r *rpcServer) SignEOTS(_ context.Context, req *proto.SignEOTSRequest) (
return &proto.SignEOTSResponse{Sig: sigBytes[:]}, nil
}

// UnsafeSignEOTS only used for testing purposes. Doesn't offer slashing protection!
func (r *rpcServer) UnsafeSignEOTS(_ context.Context, req *proto.SignEOTSRequest) (
*proto.SignEOTSResponse, error) {
sig, err := r.em.UnsafeSignEOTS(req.Uid, req.ChainId, req.Msg, req.Height, req.Passphrase)
if err != nil {
return nil, err
}

sigBytes := sig.Bytes()

return &proto.SignEOTSResponse{Sig: sigBytes[:]}, nil
}

// SignSchnorrSig signs a Schnorr sig with the EOTS private key
func (r *rpcServer) SignSchnorrSig(_ context.Context, req *proto.SignSchnorrSigRequest) (
*proto.SignSchnorrSigResponse, error) {
Expand Down
12 changes: 11 additions & 1 deletion finality-provider/service/fp_instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -695,8 +695,18 @@ func (fp *FinalityProviderInstance) TestSubmitFinalitySignatureAndExtractPrivKey
return nil, nil, fmt.Errorf("failed to get public randomness inclusion proof: %w", err)
}

unsafeSign := func(b *types.BlockInfo) (*bbntypes.SchnorrEOTSSig, error) {
msgToSign := getMsgToSignForVote(b.Height, b.Hash)
sig, err := fp.em.UnsafeSignEOTS(fp.btcPk.MustMarshal(), fp.GetChainID(), msgToSign, b.Height, fp.passphrase)
if err != nil {
return nil, fmt.Errorf("failed to sign EOTS: %w", err)
}

return bbntypes.NewSchnorrEOTSSigFromModNScalar(sig), nil
}

// sign block
eotsSig, err := fp.signFinalitySig(b)
eotsSig, err := unsafeSign(b)
if err != nil {
return nil, nil, err
}
Expand Down
13 changes: 5 additions & 8 deletions itest/e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,14 +98,11 @@ func TestDoubleSigning(t *testing.T) {
Height: finalizedBlocks[0].Height,
Hash: datagen.GenRandomByteArray(r, 32),
}
_, _, err = fpIns.TestSubmitFinalitySignatureAndExtractPrivKey(b)
require.Error(t, err)

// todo(lazar): implement UnsafeSignEOTS, containing logic without double sign protection
//require.NoError(t, err)
//require.NotNil(t, extractedKey)
//localKey := tm.GetFpPrivKey(t, fpIns.GetBtcPkBIP340().MustMarshal())
//require.True(t, localKey.Key.Equals(&extractedKey.Key) || localKey.Key.Negate().Equals(&extractedKey.Key))
_, extractedKey, err = fpIns.TestSubmitFinalitySignatureAndExtractPrivKey(b)
Copy link
Member

@gitferry gitferry Dec 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking that maybe we should keep an option to use SignEOTS within TestSubmitFinalitySignatureAndExtractPrivKey other than only the unsafe one so that we can test if the slashing protection works

require.NoError(t, err)
require.NotNil(t, extractedKey)
localKey := tm.GetFpPrivKey(t, fpIns.GetBtcPkBIP340().MustMarshal())
require.True(t, localKey.Key.Equals(&extractedKey.Key) || localKey.Key.Negate().Equals(&extractedKey.Key))

t.Logf("the equivocation attack is successful")
}
Expand Down
Loading