From 4c637a340ab6f4f322e41c6c4a2913cf6748f1f3 Mon Sep 17 00:00:00 2001 From: ron Date: Wed, 20 Nov 2024 19:55:02 +0800 Subject: [PATCH 1/5] 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 2/5] 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 3/5] 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 4/5] 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 5/5] 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 {