wormhole/wormchain/x/wormhole/keeper/vaa_test.go

346 lines
10 KiB
Go

package keeper_test
import (
"crypto/ecdsa"
"crypto/rand"
"encoding/binary"
"fmt"
"testing"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/stretchr/testify/assert"
keepertest "github.com/wormhole-foundation/wormchain/testutil/keeper"
"github.com/wormhole-foundation/wormchain/x/wormhole/keeper"
"github.com/wormhole-foundation/wormchain/x/wormhole/types"
"github.com/wormhole-foundation/wormhole/sdk/vaa"
)
func TestCalculateQuorum(t *testing.T) {
tests := []struct {
guardians int
quorum int
}{
{guardians: 0, quorum: 1},
{guardians: 1, quorum: 1},
{guardians: 2, quorum: 2},
{guardians: 3, quorum: 3},
{guardians: 4, quorum: 3},
{guardians: 5, quorum: 4},
{guardians: 6, quorum: 5},
{guardians: 7, quorum: 5},
{guardians: 8, quorum: 6},
{guardians: 9, quorum: 7},
{guardians: 10, quorum: 7},
{guardians: 19, quorum: 13},
}
for _, tc := range tests {
t.Run(fmt.Sprintf("%v", tc.guardians), func(t *testing.T) {
quorum := keeper.CalculateQuorum(tc.guardians)
assert.Equal(t, tc.quorum, quorum)
})
}
}
func TestKeeperCalculateQuorum(t *testing.T) {
privKey1, _ := ecdsa.GenerateKey(crypto.S256(), rand.Reader)
addr1 := crypto.PubkeyToAddress(privKey1.PublicKey)
addrsBytes := [][]byte{}
addrsBytes = append(addrsBytes, addr1.Bytes())
tests := []struct {
label string
guardianSet types.GuardianSet
guardianSetIndex uint32
quorum int
willError bool
err error
}{
{label: "HappyPath",
guardianSet: types.GuardianSet{Index: 0, Keys: addrsBytes, ExpirationTime: 0},
guardianSetIndex: 0,
quorum: 1,
willError: false},
{label: "GuardianSetNotFound",
guardianSet: types.GuardianSet{Index: 0, Keys: addrsBytes, ExpirationTime: 0},
guardianSetIndex: 1,
willError: true,
err: types.ErrGuardianSetNotFound},
{label: "GuardianSetExpired",
guardianSet: types.GuardianSet{Index: 0, Keys: addrsBytes, ExpirationTime: 100},
guardianSetIndex: 0,
willError: true,
err: types.ErrGuardianSetExpired},
}
for _, tc := range tests {
t.Run(tc.label, func(t *testing.T) {
keeper, ctx := keepertest.WormholeKeeper(t)
keeper.AppendGuardianSet(ctx, tc.guardianSet)
quorum, _, err := keeper.CalculateQuorum(ctx, tc.guardianSetIndex)
if tc.willError == true {
assert.NotNil(t, err)
assert.Equal(t, err, tc.err)
} else {
assert.Nil(t, err)
assert.Equal(t, quorum, tc.quorum)
}
})
}
}
func sign(data common.Hash, key *ecdsa.PrivateKey, index uint8) *vaa.Signature {
sig, err := crypto.Sign(data.Bytes(), key)
if err != nil {
panic(err)
}
sigData := [65]byte{}
copy(sigData[:], sig)
return &vaa.Signature{
Index: index,
Signature: sigData,
}
}
func TestVerifySignature(t *testing.T) {
payload := vaa.SigningMsg([]byte{97, 97, 97, 97, 97, 97})
privKey1, _ := ecdsa.GenerateKey(crypto.S256(), rand.Reader)
privKey2, _ := ecdsa.GenerateKey(crypto.S256(), rand.Reader)
addr1 := crypto.PubkeyToAddress(privKey1.PublicKey)
addrsBytes := [][]byte{}
addrsBytes = append(addrsBytes, addr1.Bytes())
tests := []struct {
label string
guardianSet types.GuardianSet
signer *ecdsa.PrivateKey
setSigIndex bool
sigIndex uint8
willError bool
err error
}{
{label: "ValidSigner",
guardianSet: types.GuardianSet{Index: 0, Keys: addrsBytes, ExpirationTime: 0},
signer: privKey1,
willError: false},
{label: "IndexOutOfBounds",
guardianSet: types.GuardianSet{Index: 0, Keys: addrsBytes, ExpirationTime: 0},
signer: privKey1,
setSigIndex: true,
sigIndex: 1,
willError: true,
err: types.ErrGuardianIndexOutOfBounds},
{label: "InvalidSigner",
guardianSet: types.GuardianSet{Index: 0, Keys: addrsBytes, ExpirationTime: 0},
signer: privKey2,
willError: true,
err: types.ErrSignaturesInvalid},
}
for _, tc := range tests {
t.Run(tc.label, func(t *testing.T) {
keeper, ctx := keepertest.WormholeKeeper(t)
keeper.AppendGuardianSet(ctx, tc.guardianSet)
// build the signature
signature := sign(payload, tc.signer, 0)
if tc.setSigIndex {
signature.Index = tc.sigIndex
}
// verify the signature
err := keeper.VerifySignature(ctx, payload.Bytes(), tc.guardianSet.Index, signature)
if tc.willError == true {
assert.NotNil(t, err)
assert.Equal(t, err, tc.err)
} else {
assert.NoError(t, err)
}
})
}
}
var lastestSequence = 1
func generateVaa(index uint32, signers []*ecdsa.PrivateKey, emitterChain vaa.ChainID, payload []byte) vaa.VAA {
v := vaa.VAA{
Version: uint8(1),
GuardianSetIndex: index,
Signatures: nil,
Timestamp: time.Unix(0, 0),
Nonce: uint32(1),
Sequence: uint64(lastestSequence),
ConsistencyLevel: uint8(32),
EmitterChain: vaa.ChainIDSolana,
EmitterAddress: vaa.Address(vaa.GovernanceEmitter),
Payload: payload,
}
lastestSequence = lastestSequence + 1
for i, key := range signers {
v.AddSignature(key, uint8(i))
}
return v
}
func resignVaa(v vaa.VAA, signers []*ecdsa.PrivateKey) vaa.VAA {
v.Signatures = []*vaa.Signature{}
for i, key := range signers {
v.AddSignature(key, uint8(i))
}
return v
}
func TestVerifyVAA(t *testing.T) {
payload := []byte{97, 97, 97, 97, 97, 97}
privKey1, _ := ecdsa.GenerateKey(crypto.S256(), rand.Reader)
privKey2, _ := ecdsa.GenerateKey(crypto.S256(), rand.Reader)
addr1 := crypto.PubkeyToAddress(privKey1.PublicKey)
addrsBytes := [][]byte{}
addrsBytes = append(addrsBytes, addr1.Bytes())
tests := []struct {
label string
guardianSet types.GuardianSet
signers []*ecdsa.PrivateKey
willError bool
}{
{label: "ValidSigner",
guardianSet: types.GuardianSet{Index: 0, Keys: addrsBytes, ExpirationTime: 0},
signers: []*ecdsa.PrivateKey{privKey1},
willError: false},
{label: "InvalidSigner",
guardianSet: types.GuardianSet{Index: 0, Keys: addrsBytes, ExpirationTime: 0},
signers: []*ecdsa.PrivateKey{privKey2},
willError: true},
{label: "InvalidGuardianSetIndex",
guardianSet: types.GuardianSet{Index: 1, Keys: addrsBytes, ExpirationTime: 0},
signers: []*ecdsa.PrivateKey{privKey1},
willError: true},
}
for _, tc := range tests {
t.Run(tc.label, func(t *testing.T) {
keeper, ctx := keepertest.WormholeKeeper(t)
vaa := generateVaa(tc.guardianSet.Index, tc.signers, vaa.ChainIDSolana, payload)
keeper.AppendGuardianSet(ctx, tc.guardianSet)
err := keeper.VerifyVAA(ctx, &vaa)
if tc.willError == true {
assert.NotNil(t, err)
} else {
assert.Nil(t, err)
}
})
}
}
func TestVerifyVAA2(t *testing.T) {
keeper, ctx := keepertest.WormholeKeeper(t)
guardians, privateKeys := createNGuardianValidator(keeper, ctx, 25)
set := createNewGuardianSet(keeper, ctx, guardians)
// check verify works
payload := []byte{97, 97, 97, 97, 97, 97}
v := generateVaa(set.Index, privateKeys, vaa.ChainIDSolana, payload)
err := keeper.VerifyVAA(ctx, &v)
assert.NoError(t, err)
// flip a bit in one of the signatures
v = generateVaa(set.Index, privateKeys, vaa.ChainIDSolana, payload)
v.Signatures[20].Signature[1] = v.Signatures[20].Signature[1] ^ 0x40
err = keeper.VerifyVAA(ctx, &v)
assert.Error(t, err)
// generate for a non existing guardian set
v = generateVaa(set.Index+1, privateKeys, vaa.ChainIDSolana, payload)
err = keeper.VerifyVAA(ctx, &v)
assert.Error(t, err)
}
func TestVerifyVAAGovernance(t *testing.T) {
keeper, ctx := keepertest.WormholeKeeper(t)
guardians, privateKeys := createNGuardianValidator(keeper, ctx, 25)
set := createNewGuardianSet(keeper, ctx, guardians)
config := types.Config{
GovernanceEmitter: vaa.GovernanceEmitter[:],
GovernanceChain: uint32(vaa.GovernanceChain),
ChainId: uint32(vaa.ChainIDWormchain),
GuardianSetExpiration: 86400,
}
keeper.SetConfig(ctx, config)
action := byte(0x12)
our_module := [32]byte{00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 01}
payload := []byte{}
// governance payload is {module_id, action, chain, payload}
payload = append(payload, our_module[:]...)
payload = append(payload, action)
chain_bz := [2]byte{}
binary.BigEndian.PutUint16(chain_bz[:], uint16(vaa.ChainIDWormchain))
payload = append(payload, chain_bz[:]...)
// custom payload
custom_payload := []byte{1, 2, 3, 4, 5}
payload = append(payload, custom_payload...)
v := generateVaa(set.Index, privateKeys, vaa.ChainID(vaa.GovernanceChain), payload)
err := keeper.VerifyVAA(ctx, &v)
assert.NoError(t, err)
parsed_action, parsed_payload, err := keeper.VerifyGovernanceVAA(ctx, &v, our_module)
assert.NoError(t, err)
assert.Equal(t, action, parsed_action)
assert.Equal(t, custom_payload, parsed_payload)
// verifying a second time will return error because of replay protection
_, _, err = keeper.VerifyGovernanceVAA(ctx, &v, our_module)
assert.ErrorIs(t, err, types.ErrVAAAlreadyExecuted)
// Expect error if module-id is different
v = generateVaa(set.Index, privateKeys, vaa.ChainID(vaa.GovernanceChain), payload)
bad_module := [32]byte{}
bad_module[31] = 0xff
_, _, err = keeper.VerifyGovernanceVAA(ctx, &v, bad_module)
assert.ErrorIs(t, err, types.ErrUnknownGovernanceModule)
// Expect error if we're not using the right governance emitter address
v = generateVaa(set.Index, privateKeys, vaa.ChainID(vaa.GovernanceChain), payload)
v.EmitterAddress[5] = 0xff
v = resignVaa(v, privateKeys)
_, _, err = keeper.VerifyGovernanceVAA(ctx, &v, our_module)
assert.ErrorIs(t, err, types.ErrInvalidGovernanceEmitter)
// Expect error if we're not using the right governance emitter chain
v = generateVaa(set.Index, privateKeys, vaa.ChainID(vaa.GovernanceChain), payload)
v.EmitterChain = vaa.ChainIDEthereum
v = resignVaa(v, privateKeys)
_, _, err = keeper.VerifyGovernanceVAA(ctx, &v, our_module)
assert.ErrorIs(t, err, types.ErrInvalidGovernanceEmitter)
// Expect error if we're using a small payload
v = generateVaa(set.Index, privateKeys, vaa.ChainID(vaa.GovernanceChain), payload[:34])
_, _, err = keeper.VerifyGovernanceVAA(ctx, &v, our_module)
assert.ErrorIs(t, err, types.ErrGovernanceHeaderTooShort)
// Expect error if we're using a different target chain
payload[33] = 0xff
payload[34] = 0xff
v = generateVaa(set.Index, privateKeys, vaa.ChainID(vaa.GovernanceChain), payload)
_, _, err = keeper.VerifyGovernanceVAA(ctx, &v, our_module)
assert.ErrorIs(t, err, types.ErrInvalidGovernanceTargetChain)
}