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

Fix for signature malleability #1335

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
4 changes: 2 additions & 2 deletions go.work.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down Expand Up @@ -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=
Expand Down
1 change: 1 addition & 0 deletions relayer/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down
49 changes: 42 additions & 7 deletions relayer/relays/beefy/parameters.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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,
Expand All @@ -89,13 +93,41 @@ 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) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Out of interest, why name the return parameters?

Copy link
Contributor Author

@yrong yrong Nov 21, 2024

Choose a reason for hiding this comment

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

Nothing special, just for return with error easily.

return v, r, s, fmt.Errorf("invalid V:%d", v)

// 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 = uint256.NewInt(0)
N.SetFromHex("0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141")
var halfN *uint256.Int = uint256.NewInt(0)
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", 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) {
var negativeS256 *uint256.Int = uint256.NewInt(0)
negativeS256 = negativeS256.Sub(N, s256)
s = negativeS256.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) {
Expand Down Expand Up @@ -132,7 +164,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)
Expand Down
50 changes: 50 additions & 0 deletions relayer/relays/beefy/parameters_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package beefy

import (
"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 {
t.Fatal(err)
}
assert.Equal(t, vAfter, v)
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)
signature := buildSignature(v, r, s)

negativeS, err := util.HexStringTo32Bytes("0x6dc0d1a7743c3328bfcfe05a2f8691e114f9143776a461ddad6e8b858bb19c1d")
negativeV := byte(28)

vAfter, rAfter, sAfter, err := cleanSignature(signature)
if err != nil {
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)
copy(signature[:], input)
return signature
}
Loading