-
Notifications
You must be signed in to change notification settings - Fork 107
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
Changes from 10 commits
4c637a3
1e0b1c4
3e2527a
aa41d7d
34a5dfd
dcce6d3
b833ea2
90192b0
fbf54b8
07ba95c
0a98e08
ce46a00
69cd774
1c26bd3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
@@ -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("clean signature: %w", err) | ||||||||||
} | ||||||||||
|
||||||||||
msg := InitialRequestParams{ | ||||||||||
Commitment: *commitment, | ||||||||||
|
@@ -89,13 +93,43 @@ func toBeefyClientCommitment(c *types.Commitment) *contracts.BeefyClientCommitme | |||||||||
} | ||||||||||
} | ||||||||||
|
||||||||||
func cleanSignature(input types.BeefySignature) (uint8, [32]byte, [32]byte) { | ||||||||||
// 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) { | ||||||||||
yrong marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||
// 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, reverted, fmt.Errorf("invalid V:%d", v) | ||||||||||
} | ||||||||||
var N *uint256.Int = uint256.NewInt(0) | ||||||||||
N.SetFromHex("0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141") | ||||||||||
yrong marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||
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 && 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 | ||||||||||
// 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 | ||||||||||
} | ||||||||||
reverted = true | ||||||||||
} | ||||||||||
return v, r, s, reverted, nil | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. snowbridge/relayer/cmd/scan_beefy.go Lines 183 to 186 in ce46a00
Just used to inspect the beefy signatures. |
||||||||||
} | ||||||||||
|
||||||||||
func (r *Request) generateValidatorAddressProof(validatorIndex int64) ([][32]byte, error) { | ||||||||||
|
@@ -132,7 +166,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("clean signature: %w", err) | ||||||||||
} | ||||||||||
account, err := r.Validators[validatorIndex].IntoEthereumAddress() | ||||||||||
if err != nil { | ||||||||||
return nil, fmt.Errorf("convert to ethereum address: %w", err) | ||||||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
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, 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) { | ||
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, 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) | ||
} | ||
|
||
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 | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Commented code?