From 4c637a340ab6f4f322e41c6c4a2913cf6748f1f3 Mon Sep 17 00:00:00 2001 From: ron Date: Wed, 20 Nov 2024 19:55:02 +0800 Subject: [PATCH 01/13] Fix for signature malleability --- relayer/relays/beefy/parameters.go | 45 +++++++++++++++++++++++++----- 1 file changed, 38 insertions(+), 7 deletions(-) diff --git a/relayer/relays/beefy/parameters.go b/relayer/relays/beefy/parameters.go index 03af798f69..5451c36c64 100644 --- a/relayer/relays/beefy/parameters.go +++ b/relayer/relays/beefy/parameters.go @@ -5,6 +5,7 @@ import ( "fmt" "math/big" + "github.com/holiman/uint256" "github.com/sirupsen/logrus" "github.com/snowfork/go-substrate-rpc-client/v4/types" "github.com/snowfork/snowbridge/relayer/contracts" @@ -63,7 +64,10 @@ func (r *Request) MakeSubmitInitialParams(valAddrIndex int64, initialBitfield [] return nil, fmt.Errorf("convert to ethereum address: %w", err) } - v, _r, s := cleanSignature(validatorSignature) + v, _r, s, err := cleanSignature(validatorSignature) + if err != nil { + return nil, fmt.Errorf("cleanSignature: %w", err) + } msg := InitialRequestParams{ Commitment: *commitment, @@ -89,13 +93,37 @@ func toBeefyClientCommitment(c *types.Commitment) *contracts.BeefyClientCommitme } } -func cleanSignature(input types.BeefySignature) (uint8, [32]byte, [32]byte) { +func cleanSignature(input types.BeefySignature) (v uint8, r [32]byte, s [32]byte, err error) { // Update signature format (Polkadot uses recovery IDs 0 or 1, Eth uses 27 or 28, so we need to add 27) // Split signature into r, s, v and add 27 to v - r := *(*[32]byte)(input[:32]) - s := *(*[32]byte)(input[32:64]) - v := byte(uint8(input[64]) + 27) - return v, r, s + r = *(*[32]byte)(input[:32]) + s = *(*[32]byte)(input[32:64]) + v = uint8(input[64]) + if v < 27 { + v += 27 + } + if v != 27 && v != 28 { + return v, r, s, fmt.Errorf("invalid V:%d", v) + } + var N *uint256.Int + N.SetFromHex("0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141") + var halfN *uint256.Int + halfN.SetFromHex("0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0") + var s256 *uint256.Int + err = s256.SetFromHex(util.BytesToHexString(s[:])) + if err != nil { + return v, r, s, fmt.Errorf("invalid S:%s", util.BytesToHexString(s[:])) + } + if s256.Gt(halfN) { + s256 = s256.Sub(N, s256) + s = s256.Bytes32() + if v%2 == 0 { + v = v - 1 + } else { + v = v + 1 + } + } + return v, r, s, nil } func (r *Request) generateValidatorAddressProof(validatorIndex int64) ([][32]byte, error) { @@ -132,7 +160,10 @@ func (r *Request) MakeSubmitFinalParams(validatorIndices []uint64, initialBitfie return nil, fmt.Errorf("signature is empty") } - v, _r, s := cleanSignature(beefySig) + v, _r, s, err := cleanSignature(beefySig) + if err != nil { + return nil, fmt.Errorf("cleanSignature: %w", err) + } account, err := r.Validators[validatorIndex].IntoEthereumAddress() if err != nil { return nil, fmt.Errorf("convert to ethereum address: %w", err) From 1e0b1c403f0ae15dab653df25c49ee7cf8fb32bb Mon Sep 17 00:00:00 2001 From: ron Date: Thu, 21 Nov 2024 00:47:48 +0800 Subject: [PATCH 02/13] Update go.mod --- go.work.sum | 4 ++-- relayer/go.mod | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/go.work.sum b/go.work.sum index 1e67bcdd1d..1baab32949 100644 --- a/go.work.sum +++ b/go.work.sum @@ -100,7 +100,6 @@ github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/fjl/gencodec v0.0.0-20230517082657-f9840df7b83e h1:bBLctRc7kr01YGvaDfgLbTwjFNW5jdp5y5rj8XXBHfY= github.com/fjl/gencodec v0.0.0-20230517082657-f9840df7b83e/go.mod h1:AzA8Lj6YtixmJWL+wkKoBGsLWy9gFrAzi4g+5bCKwpY= -github.com/fjl/memsize v0.0.2/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0= github.com/flosch/pongo2/v4 v4.0.2 h1:gv+5Pe3vaSVmiJvh/BZa82b7/00YUGm0PIyVVLop0Hw= github.com/flosch/pongo2/v4 v4.0.2/go.mod h1:B5ObFANs/36VwxxlgKpdchIJHMvHB562PW+BWPhwZD8= github.com/garslo/gogen v0.0.0-20170306192744-1d203ffc1f61 h1:IZqZOB2fydHte3kUgxrzK5E1fW7RQGeDwE8F/ZZnUYc= @@ -173,7 +172,8 @@ github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+l github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/serf v0.10.1 h1:Z1H2J60yRKvfDYAOZLd2MU0ND4AH/WDz7xYHDWQsIPY= github.com/hashicorp/serf v0.10.1/go.mod h1:yL2t6BqATOLGc5HF7qbFkTfXoPIY0WZdWHfEvMqbG+4= -github.com/holiman/billy v0.0.0-20240216141850-2abb0c79d3c4/go.mod h1:5GuXa7vkL8u9FkFuWdVvfR5ix8hRB7DbOAaYULamFpc= +github.com/holiman/uint256 v1.3.1 h1:JfTzmih28bittyHM8z360dCjIA9dbPIBlcTI6lmctQs= +github.com/holiman/uint256 v1.3.1/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hydrogen18/memlistener v1.0.0 h1:JR7eDj8HD6eXrc5fWLbSUnfcQFL06PYvCc0DKQnWfaU= github.com/hydrogen18/memlistener v1.0.0/go.mod h1:qEIFzExnS6016fRpRfxrExeVn2gbClQA99gQhnIcdhE= diff --git a/relayer/go.mod b/relayer/go.mod index cfc2c5bb8d..9c0fffbe24 100644 --- a/relayer/go.mod +++ b/relayer/go.mod @@ -20,6 +20,7 @@ require ( github.com/stretchr/testify v1.8.4 golang.org/x/exp v0.0.0-20240110193028-0dcbfd608b1e golang.org/x/sync v0.6.0 + github.com/holiman/uint256 v1.3.1 ) require ( From 3e2527ad8ad9b6049b52038efb9563d9a4a8f130 Mon Sep 17 00:00:00 2001 From: ron Date: Thu, 21 Nov 2024 02:09:15 +0800 Subject: [PATCH 03/13] Add test --- relayer/relays/beefy/parameters.go | 6 ++-- relayer/relays/beefy/parameters_test.go | 47 +++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 3 deletions(-) create mode 100644 relayer/relays/beefy/parameters_test.go diff --git a/relayer/relays/beefy/parameters.go b/relayer/relays/beefy/parameters.go index 5451c36c64..c8dac59762 100644 --- a/relayer/relays/beefy/parameters.go +++ b/relayer/relays/beefy/parameters.go @@ -105,11 +105,11 @@ func cleanSignature(input types.BeefySignature) (v uint8, r [32]byte, s [32]byte if v != 27 && v != 28 { return v, r, s, fmt.Errorf("invalid V:%d", v) } - var N *uint256.Int + var N *uint256.Int = uint256.NewInt(0) N.SetFromHex("0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141") - var halfN *uint256.Int + var halfN *uint256.Int = uint256.NewInt(0) halfN.SetFromHex("0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0") - var s256 *uint256.Int + var s256 *uint256.Int = uint256.NewInt(0) err = s256.SetFromHex(util.BytesToHexString(s[:])) if err != nil { return v, r, s, fmt.Errorf("invalid S:%s", util.BytesToHexString(s[:])) diff --git a/relayer/relays/beefy/parameters_test.go b/relayer/relays/beefy/parameters_test.go new file mode 100644 index 0000000000..ae52549471 --- /dev/null +++ b/relayer/relays/beefy/parameters_test.go @@ -0,0 +1,47 @@ +package beefy + +import ( + "fmt" + "testing" + + "github.com/snowfork/go-substrate-rpc-client/v4/types" + "github.com/snowfork/snowbridge/relayer/relays/util" +) + +func TestCleanSignatureNochange(t *testing.T) { + r, err := util.HexStringTo32Bytes("0xc1d9e2b5dd63860d27c38a8b276e5a5ab5e19a97452b0cb24094613bcbd517d8") + s, err := util.HexStringTo32Bytes("0x6dc0d1a7743c3328bfcfe05a2f8691e114f9143776a461ddad6e8b858bb19c1d") + v := byte(28) + if err != nil { + return + } + var input []byte + input = append(r[:], s[:]...) + input = append(input, v) + var signature types.BeefySignature + copy(signature[:], input) + fmt.Println(signature) + _v, r, s, err := cleanSignature(signature) + fmt.Println(_v) + fmt.Println(util.BytesToHexString(r[:])) + fmt.Println(util.BytesToHexString(s[:])) +} + +func TestCleanSignatureWithRConverted(t *testing.T) { + r, err := util.HexStringTo32Bytes("0xc1d9e2b5dd63860d27c38a8b276e5a5ab5e19a97452b0cb24094613bcbd517d8") + s, err := util.HexStringTo32Bytes("0x923f2e588bc3ccd740301fa5d0796e1da5b5c8af38a43e5e1263d3074484a524") + v := byte(27) + if err != nil { + return + } + var input []byte + input = append(r[:], s[:]...) + input = append(input, v) + var signature types.BeefySignature + copy(signature[:], input) + fmt.Println(signature) + _v, r, s, err := cleanSignature(signature) + fmt.Println(_v) + fmt.Println(util.BytesToHexString(r[:])) + fmt.Println(util.BytesToHexString(s[:])) +} From aa41d7d32f0955317f2201336c8b78040d3c76e9 Mon Sep 17 00:00:00 2001 From: ron Date: Thu, 21 Nov 2024 09:14:23 +0800 Subject: [PATCH 04/13] Improve test --- relayer/relays/beefy/parameters_test.go | 43 +++++++++++++------------ 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/relayer/relays/beefy/parameters_test.go b/relayer/relays/beefy/parameters_test.go index ae52549471..9a325dacd1 100644 --- a/relayer/relays/beefy/parameters_test.go +++ b/relayer/relays/beefy/parameters_test.go @@ -1,47 +1,50 @@ package beefy import ( - "fmt" "testing" "github.com/snowfork/go-substrate-rpc-client/v4/types" "github.com/snowfork/snowbridge/relayer/relays/util" + "github.com/stretchr/testify/assert" ) func TestCleanSignatureNochange(t *testing.T) { r, err := util.HexStringTo32Bytes("0xc1d9e2b5dd63860d27c38a8b276e5a5ab5e19a97452b0cb24094613bcbd517d8") s, err := util.HexStringTo32Bytes("0x6dc0d1a7743c3328bfcfe05a2f8691e114f9143776a461ddad6e8b858bb19c1d") v := byte(28) + signature := buildSignature(v, r, s) + vAfter, rAfter, sAfter, err := cleanSignature(signature) if err != nil { - return + t.Fatal(err) } - var input []byte - input = append(r[:], s[:]...) - input = append(input, v) - var signature types.BeefySignature - copy(signature[:], input) - fmt.Println(signature) - _v, r, s, err := cleanSignature(signature) - fmt.Println(_v) - fmt.Println(util.BytesToHexString(r[:])) - fmt.Println(util.BytesToHexString(s[:])) + assert.Equal(t, vAfter, v) + assert.Equal(t, rAfter, r) + assert.Equal(t, sAfter, s) + } -func TestCleanSignatureWithRConverted(t *testing.T) { +func TestCleanSignatureWithSConverted(t *testing.T) { r, err := util.HexStringTo32Bytes("0xc1d9e2b5dd63860d27c38a8b276e5a5ab5e19a97452b0cb24094613bcbd517d8") s, err := util.HexStringTo32Bytes("0x923f2e588bc3ccd740301fa5d0796e1da5b5c8af38a43e5e1263d3074484a524") v := byte(27) + signature := buildSignature(v, r, s) + + negativeS, err := util.HexStringTo32Bytes("0x6dc0d1a7743c3328bfcfe05a2f8691e114f9143776a461ddad6e8b858bb19c1d") + negativeV := byte(28) + + vAfter, rAfter, sAfter, err := cleanSignature(signature) if err != nil { - return + t.Fatal(err) } + assert.Equal(t, vAfter, negativeV) + assert.Equal(t, rAfter, r) + assert.Equal(t, sAfter, negativeS) +} + +func buildSignature(v uint8, r [32]byte, s [32]byte) (signature types.BeefySignature) { var input []byte input = append(r[:], s[:]...) input = append(input, v) - var signature types.BeefySignature copy(signature[:], input) - fmt.Println(signature) - _v, r, s, err := cleanSignature(signature) - fmt.Println(_v) - fmt.Println(util.BytesToHexString(r[:])) - fmt.Println(util.BytesToHexString(s[:])) + return signature } From 34a5dfde5a099f8417181d245ed40ddc087a44c3 Mon Sep 17 00:00:00 2001 From: ron Date: Thu, 21 Nov 2024 09:31:53 +0800 Subject: [PATCH 05/13] Add comments --- relayer/relays/beefy/parameters.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/relayer/relays/beefy/parameters.go b/relayer/relays/beefy/parameters.go index c8dac59762..eb381d70f0 100644 --- a/relayer/relays/beefy/parameters.go +++ b/relayer/relays/beefy/parameters.go @@ -114,9 +114,13 @@ func cleanSignature(input types.BeefySignature) (v uint8, r [32]byte, s [32]byte if err != nil { return v, r, s, fmt.Errorf("invalid S:%s", util.BytesToHexString(s[:])) } + // If polkadot library generates malleable signatures, such as s-values in the upper range, calculate a new s-value + // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or + // vice versa. if s256.Gt(halfN) { - s256 = s256.Sub(N, s256) - s = s256.Bytes32() + var negativeS256 *uint256.Int = uint256.NewInt(0) + negativeS256 = negativeS256.Sub(N, s256) + s = negativeS256.Bytes32() if v%2 == 0 { v = v - 1 } else { From dcce6d33e3178cd4f7abdb25bd4634960d9b2f72 Mon Sep 17 00:00:00 2001 From: ron Date: Mon, 25 Nov 2024 16:56:06 +0000 Subject: [PATCH 06/13] Add command for scan beefy signatures --- relayer/cmd/root.go | 2 +- relayer/cmd/scan_beefy.go | 109 +++++++++++++++++++--- relayer/relays/beefy/config.go | 5 +- relayer/relays/beefy/main.go | 2 +- relayer/relays/beefy/parameters.go | 13 +-- relayer/relays/beefy/parameters_test.go | 4 +- relayer/relays/beefy/polkadot-listener.go | 5 +- 7 files changed, 110 insertions(+), 30 deletions(-) diff --git a/relayer/cmd/root.go b/relayer/cmd/root.go index 85b18eb658..67e6a0402c 100644 --- a/relayer/cmd/root.go +++ b/relayer/cmd/root.go @@ -22,9 +22,9 @@ var rootCmd = &cobra.Command{ func init() { rootCmd.AddCommand(run.Command()) rootCmd.AddCommand(getBlockCmd()) - //rootCmd.AddCommand(fetchMessagesCmd()) rootCmd.AddCommand(subBeefyCmd()) rootCmd.AddCommand(scanBeefyCmd()) + rootCmd.AddCommand(scanSingleBeefyBlockCmd()) rootCmd.AddCommand(leafCmd()) rootCmd.AddCommand(basicChannelLeafProofCmd()) rootCmd.AddCommand(parachainHeadProofCmd()) diff --git a/relayer/cmd/scan_beefy.go b/relayer/cmd/scan_beefy.go index 5c54550739..c6f200ae02 100644 --- a/relayer/cmd/scan_beefy.go +++ b/relayer/cmd/scan_beefy.go @@ -2,14 +2,17 @@ package cmd import ( "context" + "fmt" "log" "os" "os/signal" "syscall" "github.com/sirupsen/logrus" + "github.com/snowfork/go-substrate-rpc-client/v4/types" "github.com/snowfork/snowbridge/relayer/chain/relaychain" "github.com/snowfork/snowbridge/relayer/relays/beefy" + "github.com/snowfork/snowbridge/relayer/relays/util" "github.com/spf13/cobra" "golang.org/x/sync/errgroup" ) @@ -25,9 +28,6 @@ func scanBeefyCmd() *cobra.Command { cmd.Flags().StringP("polkadot-url", "p", "ws://127.0.0.1:9944", "Polkadot URL.") cmd.Flags().Uint64P("beefy-block", "b", 0, "Beefy block.") cmd.MarkFlagRequired("beefy-block") - cmd.Flags().Uint64P("validator-set-id", "v", 0, "Validator set id.") - cmd.MarkFlagRequired("validator-set-id") - cmd.Flags().Uint64P("fast-forward-depth", "f", 10000, "Fast forward depth.") return cmd } @@ -43,24 +43,19 @@ func ScanBeefyFn(cmd *cobra.Command, _ []string) error { relaychainConn := relaychain.NewConnection(polkadotUrl) relaychainConn.Connect(ctx) - fastForwardDepth, _ := cmd.Flags().GetUint64("fast-forward-depth") - config := beefy.SourceConfig{ - FastForwardDepth: fastForwardDepth, - } + config := beefy.SourceConfig{} polkadotListener := beefy.NewPolkadotListener( &config, relaychainConn, ) beefyBlock, _ := cmd.Flags().GetUint64("beefy-block") - validatorSetID, _ := cmd.Flags().GetUint64("validator-set-id") logrus.WithFields(logrus.Fields{ - "polkadot-url": polkadotUrl, - "beefy-block": beefyBlock, - "validator-set-id": validatorSetID, + "polkadot-url": polkadotUrl, + "beefy-block": beefyBlock, }).Info("Connected to relaychain.") - commitments, err := polkadotListener.Start(ctx, eg, beefyBlock, validatorSetID) + commitments, err := polkadotListener.Start(ctx, eg, beefyBlock) if err != nil { logrus.WithError(err).Fatalf("could not start") } @@ -103,3 +98,93 @@ func ScanBeefyFn(cmd *cobra.Command, _ []string) error { return nil } + +func scanSingleBeefyBlockCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "scan-single-beefy-block", + Short: "Scan a single block which contains beefy commitment", + Args: cobra.ExactArgs(0), + RunE: ScanSingleBeefyBlockFn, + } + + cmd.Flags().StringP("polkadot-url", "p", "ws://127.0.0.1:9944", "Polkadot URL.") + cmd.Flags().Uint64P("beefy-block", "b", 0, "Beefy block.") + cmd.MarkFlagRequired("beefy-block") + return cmd +} + +func ScanSingleBeefyBlockFn(cmd *cobra.Command, _ []string) error { + ctx := cmd.Context() + log.SetOutput(logrus.WithFields(logrus.Fields{"logger": "stdlib"}).WriterLevel(logrus.InfoLevel)) + logrus.SetLevel(logrus.DebugLevel) + + polkadotUrl, _ := cmd.Flags().GetString("polkadot-url") + relaychainConn := relaychain.NewConnection(polkadotUrl) + err := relaychainConn.Connect(ctx) + if err != nil { + fmt.Errorf("connect: %w", err) + return err + } + api := relaychainConn.API() + // metadata := relaychainConn.Metadata() + + beefyBlockNumber, _ := cmd.Flags().GetUint64("beefy-block") + logrus.WithFields(logrus.Fields{ + "polkadot-url": polkadotUrl, + "beefy-block": beefyBlockNumber, + }).Info("Connected to relaychain.") + + beefyBlockHash, err := api.RPC.Chain.GetBlockHash(beefyBlockNumber) + if err != nil { + return fmt.Errorf("fetch hash: %w", err) + } + + beefyBlock, err := api.RPC.Chain.GetBlock(beefyBlockHash) + if err != nil { + return fmt.Errorf("fetch block: %w", err) + } + + var commitment *types.SignedCommitment + for j := range beefyBlock.Justifications { + sc := types.OptionalSignedCommitment{} + if beefyBlock.Justifications[j].EngineID() == "BEEF" { + err := types.DecodeFromBytes(beefyBlock.Justifications[j].Payload(), &sc) + if err != nil { + return fmt.Errorf("decode BEEFY signed commitment: %w", err) + } + ok, value := sc.Unwrap() + if ok { + commitment = &value + } + } + } + if commitment == nil { + return fmt.Errorf("beefy block without a valid commitment") + } + if len(commitment.Signatures) == 0 { + return fmt.Errorf("no signature in the commitment") + } + var emptyNum uint + var errNum uint + for _, s := range commitment.Signatures { + ok, beefySig := s.Unwrap() + if !ok { + logrus.Warn("beefy signature is empty") + emptyNum++ + continue + } + s_before := util.BytesToHexString(beefySig[32:64]) + _, _, s, err := beefy.CleanSignature(beefySig) + if err != nil { + logrus.WithError(err).Warn("cleanSignature") + errNum++ + } + s_after := util.BytesToHexString(s[:]) + if s_before != s_after { + logrus.Info(fmt.Sprintf("s before clean:%s", s_before)) + logrus.Info(fmt.Sprintf("s after clean:%s", s_after)) + } + } + logrus.Info(fmt.Sprintf("number of total signatures:%d,empty signatures:%d,invalid signatures:%d", len(commitment.Signatures), emptyNum, errNum)) + return nil +} diff --git a/relayer/relays/beefy/config.go b/relayer/relays/beefy/config.go index cf8735a84b..d4aaf053e9 100644 --- a/relayer/relays/beefy/config.go +++ b/relayer/relays/beefy/config.go @@ -2,6 +2,7 @@ package beefy import ( "fmt" + "github.com/snowfork/snowbridge/relayer/config" ) @@ -12,10 +13,6 @@ type Config struct { type SourceConfig struct { Polkadot config.PolkadotConfig `mapstructure:"polkadot"` - // Depth to ignore the beefy updates too far away (in number of blocks) - FastForwardDepth uint64 `mapstructure:"fast-forward-depth"` - // Period to sample the beefy updates (in number of blocks) - UpdatePeriod uint64 `mapstructure:"update-period"` } type SinkConfig struct { diff --git a/relayer/relays/beefy/main.go b/relayer/relays/beefy/main.go index b379ce7461..14bf81b51a 100644 --- a/relayer/relays/beefy/main.go +++ b/relayer/relays/beefy/main.go @@ -68,7 +68,7 @@ func (relay *Relay) Start(ctx context.Context, eg *errgroup.Group) error { "validatorSetID": initialState.CurrentValidatorSetID, }).Info("Retrieved current BeefyClient state") - requests, err := relay.polkadotListener.Start(ctx, eg, initialState.LatestBeefyBlock, initialState.CurrentValidatorSetID) + requests, err := relay.polkadotListener.Start(ctx, eg, initialState.LatestBeefyBlock) if err != nil { return fmt.Errorf("initialize polkadot listener: %w", err) } diff --git a/relayer/relays/beefy/parameters.go b/relayer/relays/beefy/parameters.go index eb381d70f0..a32df80884 100644 --- a/relayer/relays/beefy/parameters.go +++ b/relayer/relays/beefy/parameters.go @@ -64,9 +64,9 @@ func (r *Request) MakeSubmitInitialParams(valAddrIndex int64, initialBitfield [] return nil, fmt.Errorf("convert to ethereum address: %w", err) } - v, _r, s, err := cleanSignature(validatorSignature) + v, _r, s, err := CleanSignature(validatorSignature) if err != nil { - return nil, fmt.Errorf("cleanSignature: %w", err) + logrus.WithError(err).Warn("cleanSignature") } msg := InitialRequestParams{ @@ -93,7 +93,7 @@ func toBeefyClientCommitment(c *types.Commitment) *contracts.BeefyClientCommitme } } -func cleanSignature(input types.BeefySignature) (v uint8, r [32]byte, s [32]byte, err error) { +func CleanSignature(input types.BeefySignature) (v uint8, r [32]byte, s [32]byte, err error) { // Update signature format (Polkadot uses recovery IDs 0 or 1, Eth uses 27 or 28, so we need to add 27) // Split signature into r, s, v and add 27 to v r = *(*[32]byte)(input[:32]) @@ -112,7 +112,7 @@ func cleanSignature(input types.BeefySignature) (v uint8, r [32]byte, s [32]byte var s256 *uint256.Int = uint256.NewInt(0) err = s256.SetFromHex(util.BytesToHexString(s[:])) if err != nil { - return v, r, s, fmt.Errorf("invalid S:%s", util.BytesToHexString(s[:])) + return v, r, s, fmt.Errorf("invalid S:%s,error is:%w", util.BytesToHexString(s[:]), err) } // If polkadot library generates malleable signatures, such as s-values in the upper range, calculate a new s-value // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or @@ -126,6 +126,7 @@ func cleanSignature(input types.BeefySignature) (v uint8, r [32]byte, s [32]byte } else { v = v + 1 } + logrus.Warn("malleable beefy signature found") } return v, r, s, nil } @@ -164,9 +165,9 @@ func (r *Request) MakeSubmitFinalParams(validatorIndices []uint64, initialBitfie return nil, fmt.Errorf("signature is empty") } - v, _r, s, err := cleanSignature(beefySig) + v, _r, s, err := CleanSignature(beefySig) if err != nil { - return nil, fmt.Errorf("cleanSignature: %w", err) + logrus.WithError(err).Warn("cleanSignature") } account, err := r.Validators[validatorIndex].IntoEthereumAddress() if err != nil { diff --git a/relayer/relays/beefy/parameters_test.go b/relayer/relays/beefy/parameters_test.go index 9a325dacd1..0fdee20bb1 100644 --- a/relayer/relays/beefy/parameters_test.go +++ b/relayer/relays/beefy/parameters_test.go @@ -13,7 +13,7 @@ func TestCleanSignatureNochange(t *testing.T) { s, err := util.HexStringTo32Bytes("0x6dc0d1a7743c3328bfcfe05a2f8691e114f9143776a461ddad6e8b858bb19c1d") v := byte(28) signature := buildSignature(v, r, s) - vAfter, rAfter, sAfter, err := cleanSignature(signature) + vAfter, rAfter, sAfter, err := CleanSignature(signature) if err != nil { t.Fatal(err) } @@ -32,7 +32,7 @@ func TestCleanSignatureWithSConverted(t *testing.T) { negativeS, err := util.HexStringTo32Bytes("0x6dc0d1a7743c3328bfcfe05a2f8691e114f9143776a461ddad6e8b858bb19c1d") negativeV := byte(28) - vAfter, rAfter, sAfter, err := cleanSignature(signature) + vAfter, rAfter, sAfter, err := CleanSignature(signature) if err != nil { t.Fatal(err) } diff --git a/relayer/relays/beefy/polkadot-listener.go b/relayer/relays/beefy/polkadot-listener.go index 24894af1e8..88dda54904 100644 --- a/relayer/relays/beefy/polkadot-listener.go +++ b/relayer/relays/beefy/polkadot-listener.go @@ -32,13 +32,12 @@ func (li *PolkadotListener) Start( ctx context.Context, eg *errgroup.Group, currentBeefyBlock uint64, - currentValidatorSetID uint64, ) (<-chan Request, error) { requests := make(chan Request, 1) eg.Go(func() error { defer close(requests) - err := li.scanCommitments(ctx, currentBeefyBlock, currentValidatorSetID, requests) + err := li.scanCommitments(ctx, currentBeefyBlock, requests) if err != nil { return err } @@ -51,7 +50,6 @@ func (li *PolkadotListener) Start( func (li *PolkadotListener) scanCommitments( ctx context.Context, currentBeefyBlock uint64, - currentValidatorSet uint64, requests chan<- Request, ) error { in, err := ScanCommitments(ctx, li.conn.Metadata(), li.conn.API(), currentBeefyBlock+1) @@ -92,7 +90,6 @@ func (li *PolkadotListener) scanCommitments( "validatorSetID": validatorSetID, "nextValidatorSetID": nextValidatorSetID, }, - "validatorSetID": currentValidatorSet, }).Info("Sending BEEFY commitment to ethereum writer") select { From b833ea2275736d50f3a6133d42db1e80f7db8eba Mon Sep 17 00:00:00 2001 From: ron Date: Mon, 25 Nov 2024 17:26:33 +0000 Subject: [PATCH 07/13] Ignore zero prefix error --- relayer/cmd/scan_beefy.go | 11 +++++------ relayer/relays/beefy/parameters.go | 20 ++++++++++---------- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/relayer/cmd/scan_beefy.go b/relayer/cmd/scan_beefy.go index c6f200ae02..2ceff82c3f 100644 --- a/relayer/cmd/scan_beefy.go +++ b/relayer/cmd/scan_beefy.go @@ -173,16 +173,15 @@ func ScanSingleBeefyBlockFn(cmd *cobra.Command, _ []string) error { emptyNum++ continue } - s_before := util.BytesToHexString(beefySig[32:64]) - _, _, s, err := beefy.CleanSignature(beefySig) + sBefore := util.BytesToHexString(beefySig[32:64]) + _, _, s, reverted, err := beefy.CleanSignature(beefySig) if err != nil { logrus.WithError(err).Warn("cleanSignature") errNum++ } - s_after := util.BytesToHexString(s[:]) - if s_before != s_after { - logrus.Info(fmt.Sprintf("s before clean:%s", s_before)) - logrus.Info(fmt.Sprintf("s after clean:%s", s_after)) + sAfter := util.BytesToHexString(s[:]) + if reverted { + logrus.Info(fmt.Sprintf("s is reverted, before clean:%s, after clean:%s", sBefore, sAfter)) } } logrus.Info(fmt.Sprintf("number of total signatures:%d,empty signatures:%d,invalid signatures:%d", len(commitment.Signatures), emptyNum, errNum)) diff --git a/relayer/relays/beefy/parameters.go b/relayer/relays/beefy/parameters.go index a32df80884..febdb0aad4 100644 --- a/relayer/relays/beefy/parameters.go +++ b/relayer/relays/beefy/parameters.go @@ -64,9 +64,9 @@ func (r *Request) MakeSubmitInitialParams(valAddrIndex int64, initialBitfield [] return nil, fmt.Errorf("convert to ethereum address: %w", err) } - v, _r, s, err := CleanSignature(validatorSignature) + v, _r, s, _, err := CleanSignature(validatorSignature) if err != nil { - logrus.WithError(err).Warn("cleanSignature") + return nil, fmt.Errorf("clean signature: %w", err) } msg := InitialRequestParams{ @@ -93,7 +93,7 @@ func toBeefyClientCommitment(c *types.Commitment) *contracts.BeefyClientCommitme } } -func CleanSignature(input types.BeefySignature) (v uint8, r [32]byte, s [32]byte, err error) { +func CleanSignature(input types.BeefySignature) (v uint8, r [32]byte, s [32]byte, reverted bool, err error) { // Update signature format (Polkadot uses recovery IDs 0 or 1, Eth uses 27 or 28, so we need to add 27) // Split signature into r, s, v and add 27 to v r = *(*[32]byte)(input[:32]) @@ -103,7 +103,7 @@ func CleanSignature(input types.BeefySignature) (v uint8, r [32]byte, s [32]byte v += 27 } if v != 27 && v != 28 { - return v, r, s, fmt.Errorf("invalid V:%d", v) + return v, r, s, reverted, fmt.Errorf("invalid V:%d", v) } var N *uint256.Int = uint256.NewInt(0) N.SetFromHex("0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141") @@ -111,8 +111,8 @@ func CleanSignature(input types.BeefySignature) (v uint8, r [32]byte, s [32]byte halfN.SetFromHex("0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0") var s256 *uint256.Int = uint256.NewInt(0) err = s256.SetFromHex(util.BytesToHexString(s[:])) - if err != nil { - return v, r, s, fmt.Errorf("invalid S:%s,error is:%w", util.BytesToHexString(s[:]), err) + if err != nil && err != uint256.ErrLeadingZero { + return v, r, s, reverted, fmt.Errorf("invalid S:%s,error is:%w", util.BytesToHexString(s[:]), err) } // If polkadot library generates malleable signatures, such as s-values in the upper range, calculate a new s-value // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or @@ -126,9 +126,9 @@ func CleanSignature(input types.BeefySignature) (v uint8, r [32]byte, s [32]byte } else { v = v + 1 } - logrus.Warn("malleable beefy signature found") + reverted = true } - return v, r, s, nil + return v, r, s, reverted, nil } func (r *Request) generateValidatorAddressProof(validatorIndex int64) ([][32]byte, error) { @@ -165,9 +165,9 @@ func (r *Request) MakeSubmitFinalParams(validatorIndices []uint64, initialBitfie return nil, fmt.Errorf("signature is empty") } - v, _r, s, err := CleanSignature(beefySig) + v, _r, s, _, err := CleanSignature(beefySig) if err != nil { - logrus.WithError(err).Warn("cleanSignature") + return nil, fmt.Errorf("clean signature: %w", err) } account, err := r.Validators[validatorIndex].IntoEthereumAddress() if err != nil { From 90192b0bf71c038f83fd44e1f1f773c912286f10 Mon Sep 17 00:00:00 2001 From: ron Date: Tue, 26 Nov 2024 08:47:39 +0000 Subject: [PATCH 08/13] Fix test --- relayer/relays/beefy/parameters_test.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/relayer/relays/beefy/parameters_test.go b/relayer/relays/beefy/parameters_test.go index 0fdee20bb1..464363c75b 100644 --- a/relayer/relays/beefy/parameters_test.go +++ b/relayer/relays/beefy/parameters_test.go @@ -13,14 +13,14 @@ func TestCleanSignatureNochange(t *testing.T) { s, err := util.HexStringTo32Bytes("0x6dc0d1a7743c3328bfcfe05a2f8691e114f9143776a461ddad6e8b858bb19c1d") v := byte(28) signature := buildSignature(v, r, s) - vAfter, rAfter, sAfter, err := CleanSignature(signature) + vAfter, rAfter, sAfter, reverted, err := CleanSignature(signature) if err != nil { t.Fatal(err) } + assert.Equal(t, reverted, false) assert.Equal(t, vAfter, v) assert.Equal(t, rAfter, r) assert.Equal(t, sAfter, s) - } func TestCleanSignatureWithSConverted(t *testing.T) { @@ -32,10 +32,11 @@ func TestCleanSignatureWithSConverted(t *testing.T) { negativeS, err := util.HexStringTo32Bytes("0x6dc0d1a7743c3328bfcfe05a2f8691e114f9143776a461ddad6e8b858bb19c1d") negativeV := byte(28) - vAfter, rAfter, sAfter, err := CleanSignature(signature) + vAfter, rAfter, sAfter, reverted, err := CleanSignature(signature) if err != nil { t.Fatal(err) } + assert.Equal(t, reverted, true) assert.Equal(t, vAfter, negativeV) assert.Equal(t, rAfter, r) assert.Equal(t, sAfter, negativeS) From fbf54b83fdfc8ab783e849676c59ab124149a806 Mon Sep 17 00:00:00 2001 From: ron Date: Tue, 26 Nov 2024 09:51:43 +0000 Subject: [PATCH 09/13] More logs --- relayer/cmd/scan_beefy.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/relayer/cmd/scan_beefy.go b/relayer/cmd/scan_beefy.go index 2ceff82c3f..efd2377ed1 100644 --- a/relayer/cmd/scan_beefy.go +++ b/relayer/cmd/scan_beefy.go @@ -166,10 +166,10 @@ func ScanSingleBeefyBlockFn(cmd *cobra.Command, _ []string) error { } var emptyNum uint var errNum uint + var revertedNum uint for _, s := range commitment.Signatures { ok, beefySig := s.Unwrap() if !ok { - logrus.Warn("beefy signature is empty") emptyNum++ continue } @@ -181,9 +181,10 @@ func ScanSingleBeefyBlockFn(cmd *cobra.Command, _ []string) error { } sAfter := util.BytesToHexString(s[:]) if reverted { + revertedNum++ logrus.Info(fmt.Sprintf("s is reverted, before clean:%s, after clean:%s", sBefore, sAfter)) } } - logrus.Info(fmt.Sprintf("number of total signatures:%d,empty signatures:%d,invalid signatures:%d", len(commitment.Signatures), emptyNum, errNum)) + logrus.Info(fmt.Sprintf("number of total signatures:%d,empty signatures:%d,invalid signatures:%d,reverted signatures:%d", len(commitment.Signatures), emptyNum, errNum, revertedNum)) return nil } From 07ba95c8265293a21e2a363a545d967e55842f56 Mon Sep 17 00:00:00 2001 From: Ron Date: Tue, 26 Nov 2024 17:54:44 +0800 Subject: [PATCH 10/13] Update relayer/relays/beefy/parameters.go Co-authored-by: Alistair Singh --- relayer/relays/beefy/parameters.go | 1 + 1 file changed, 1 insertion(+) diff --git a/relayer/relays/beefy/parameters.go b/relayer/relays/beefy/parameters.go index febdb0aad4..3b77d0ddad 100644 --- a/relayer/relays/beefy/parameters.go +++ b/relayer/relays/beefy/parameters.go @@ -93,6 +93,7 @@ func toBeefyClientCommitment(c *types.Commitment) *contracts.BeefyClientCommitme } } +// Implementation derived from https://github.com/OpenZeppelin/openzeppelin-contracts/blob/5bb3f3e788c6b2c806d562ef083b438354f969d7/contracts/utils/cryptography/ECDSA.sol#L139-L145 func CleanSignature(input types.BeefySignature) (v uint8, r [32]byte, s [32]byte, reverted bool, err error) { // Update signature format (Polkadot uses recovery IDs 0 or 1, Eth uses 27 or 28, so we need to add 27) // Split signature into r, s, v and add 27 to v From 0a98e081a61db4a7350d6e9eaf685c764121b99d Mon Sep 17 00:00:00 2001 From: Ron Date: Tue, 26 Nov 2024 19:54:08 +0800 Subject: [PATCH 11/13] Update relayer/relays/beefy/parameters.go Co-authored-by: Alistair Singh --- relayer/relays/beefy/parameters.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/relayer/relays/beefy/parameters.go b/relayer/relays/beefy/parameters.go index 3b77d0ddad..4023e89841 100644 --- a/relayer/relays/beefy/parameters.go +++ b/relayer/relays/beefy/parameters.go @@ -107,7 +107,7 @@ func CleanSignature(input types.BeefySignature) (v uint8, r [32]byte, s [32]byte return v, r, s, reverted, fmt.Errorf("invalid V:%d", v) } var N *uint256.Int = uint256.NewInt(0) - N.SetFromHex("0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141") + N.SetFromHex("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141") var halfN *uint256.Int = uint256.NewInt(0) halfN.SetFromHex("0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0") var s256 *uint256.Int = uint256.NewInt(0) From ce46a00c5fb83d759d9e4dba119f72bd8e2570c9 Mon Sep 17 00:00:00 2001 From: ron Date: Tue, 26 Nov 2024 15:19:25 +0000 Subject: [PATCH 12/13] Test ecrecover with malleable signature --- relayer/relays/beefy/parameters_test.go | 45 +++++++++++++++++-------- 1 file changed, 31 insertions(+), 14 deletions(-) diff --git a/relayer/relays/beefy/parameters_test.go b/relayer/relays/beefy/parameters_test.go index 464363c75b..2b1f9d5dff 100644 --- a/relayer/relays/beefy/parameters_test.go +++ b/relayer/relays/beefy/parameters_test.go @@ -3,43 +3,60 @@ package beefy import ( "testing" + "github.com/ethereum/go-ethereum/crypto" "github.com/snowfork/go-substrate-rpc-client/v4/types" "github.com/snowfork/snowbridge/relayer/relays/util" "github.com/stretchr/testify/assert" ) func TestCleanSignatureNochange(t *testing.T) { - r, err := util.HexStringTo32Bytes("0xc1d9e2b5dd63860d27c38a8b276e5a5ab5e19a97452b0cb24094613bcbd517d8") - s, err := util.HexStringTo32Bytes("0x6dc0d1a7743c3328bfcfe05a2f8691e114f9143776a461ddad6e8b858bb19c1d") - v := byte(28) + hash, _ := util.HexStringTo32Bytes("0x3ea2f1d0abf3fc66cf29eebb70cbd4e7fe762ef8a09bcc06c8edf641230afec0") + r, _ := util.HexStringTo32Bytes("0xc1d9e2b5dd63860d27c38a8b276e5a5ab5e19a97452b0cb24094613bcbd517d8") + s, _ := util.HexStringTo32Bytes("0x6dc0d1a7743c3328bfcfe05a2f8691e114f9143776a461ddad6e8b858bb19c1d") + v := uint8(1) signature := buildSignature(v, r, s) + publicKey, err := crypto.Ecrecover(hash[:], signature[:]) + if err != nil { + t.Fatal(err) + } + assert.Equal(t, len(publicKey), 65) vAfter, rAfter, sAfter, reverted, err := CleanSignature(signature) if err != nil { t.Fatal(err) } assert.Equal(t, reverted, false) - assert.Equal(t, vAfter, v) + assert.Equal(t, vAfter, v+27) assert.Equal(t, rAfter, r) assert.Equal(t, sAfter, s) } func TestCleanSignatureWithSConverted(t *testing.T) { - r, err := util.HexStringTo32Bytes("0xc1d9e2b5dd63860d27c38a8b276e5a5ab5e19a97452b0cb24094613bcbd517d8") - s, err := util.HexStringTo32Bytes("0x923f2e588bc3ccd740301fa5d0796e1da5b5c8af38a43e5e1263d3074484a524") - v := byte(27) + hash, _ := util.HexStringTo32Bytes("0x3ea2f1d0abf3fc66cf29eebb70cbd4e7fe762ef8a09bcc06c8edf641230afec0") + r, _ := util.HexStringTo32Bytes("0xc1d9e2b5dd63860d27c38a8b276e5a5ab5e19a97452b0cb24094613bcbd517d8") + s, _ := util.HexStringTo32Bytes("0x6dc0d1a7743c3328bfcfe05a2f8691e114f9143776a461ddad6e8b858bb19c1d") + v := uint8(1) signature := buildSignature(v, r, s) - - negativeS, err := util.HexStringTo32Bytes("0x6dc0d1a7743c3328bfcfe05a2f8691e114f9143776a461ddad6e8b858bb19c1d") - negativeV := byte(28) - - vAfter, rAfter, sAfter, reverted, err := CleanSignature(signature) + publicKey, err := crypto.Ecrecover(hash[:], signature[:]) + if err != nil { + t.Fatal(err) + } + negativeS, _ := util.HexStringTo32Bytes("0x923f2e588bc3ccd740301fa5d0796e1da5b5c8af38a43e5e1263d3074484a524") + negativeV := byte(0) + negativeSignature := buildSignature(negativeV, r, negativeS) + vAfter, rAfter, sAfter, reverted, err := CleanSignature(negativeSignature) if err != nil { t.Fatal(err) } assert.Equal(t, reverted, true) - assert.Equal(t, vAfter, negativeV) + assert.Equal(t, vAfter, v+27) assert.Equal(t, rAfter, r) - assert.Equal(t, sAfter, negativeS) + assert.Equal(t, sAfter, s) + publicKeyAfter, err := crypto.Ecrecover(hash[:], negativeSignature[:]) + if err != nil { + t.Fatal(err) + } + assert.Equal(t, len(publicKeyAfter), 65) + assert.Equal(t, publicKey, publicKeyAfter) } func buildSignature(v uint8, r [32]byte, s [32]byte) (signature types.BeefySignature) { From 69cd77490d49c691f26ad170dc5f20054690f0f6 Mon Sep 17 00:00:00 2001 From: ron Date: Tue, 3 Dec 2024 13:51:27 +0800 Subject: [PATCH 13/13] Update beefy config --- web/packages/test/config/beefy-relay.json | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/web/packages/test/config/beefy-relay.json b/web/packages/test/config/beefy-relay.json index 19a459c04c..5b2db16deb 100644 --- a/web/packages/test/config/beefy-relay.json +++ b/web/packages/test/config/beefy-relay.json @@ -2,9 +2,7 @@ "source": { "polkadot": { "endpoint": "ws://127.0.0.1:9944" - }, - "fast-forward-depth": 20, - "update-period": 0 + } }, "sink": { "ethereum": {