new VAA format in Golang

This commit is contained in:
Hendrik Hofstadt 2020-08-14 22:45:45 +02:00
parent ca4e4a3243
commit 2a096790d3
2 changed files with 86 additions and 65 deletions

View File

@ -4,12 +4,10 @@ import (
"bytes" "bytes"
"encoding/binary" "encoding/binary"
"fmt" "fmt"
"github.com/certusone/wormhole/bridge/third_party/chainlink/ethschnorr"
"github.com/certusone/wormhole/bridge/third_party/chainlink/secp256k1" "github.com/certusone/wormhole/bridge/third_party/chainlink/secp256k1"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"go.dedis.ch/kyber/v3" "go.dedis.ch/kyber/v3"
"go.dedis.ch/kyber/v3/util/key"
"io" "io"
"math" "math"
"math/big" "math/big"
@ -23,8 +21,8 @@ type (
Version uint8 Version uint8
// GuardianSetIndex is the index of the guardian set that signed this VAA // GuardianSetIndex is the index of the guardian set that signed this VAA
GuardianSetIndex uint32 GuardianSetIndex uint32
// Signature is the signature of the guardian set // SignatureData is the signature of the guardian set
Signature *Signature Signatures []*Signature
// Timestamp when the VAA was created // Timestamp when the VAA was created
Timestamp time.Time Timestamp time.Time
@ -41,12 +39,12 @@ type (
// chain is < 32bytes the value is zero-padded on the left. // chain is < 32bytes the value is zero-padded on the left.
Address [32]byte Address [32]byte
// Signature of a VAA // Signature of a single guardian
Signature struct { Signature struct {
// Sig is the signature field of a Schnorr signature // Index of the validator
Sig [32]byte Index uint8
// Address is the R equivalent in our Schnorr signature schema // Signature data
Address common.Address Signature [65]byte
} }
// AssetMeta describes an asset within the Wormhole protocol // AssetMeta describes an asset within the Wormhole protocol
@ -105,9 +103,7 @@ func ParseVAA(data []byte) (*VAA, error) {
if len(data) < minVAALength { if len(data) < minVAALength {
return nil, fmt.Errorf("VAA is too short") return nil, fmt.Errorf("VAA is too short")
} }
v := &VAA{ v := &VAA{}
Signature: &Signature{},
}
v.Version = data[0] v.Version = data[0]
if v.Version != supportedVAAVersion { if v.Version != supportedVAAVersion {
@ -120,11 +116,27 @@ func ParseVAA(data []byte) (*VAA, error) {
return nil, fmt.Errorf("failed to read guardian set index: %w", err) return nil, fmt.Errorf("failed to read guardian set index: %w", err)
} }
if n, err := reader.Read(v.Signature.Sig[:]); err != nil || n != 32 { lenSignatures, er := reader.ReadByte()
return nil, fmt.Errorf("failed to read signature sig field: %w", err) if er != nil {
return nil, fmt.Errorf("failed to read signature length")
} }
if n, err := reader.Read(v.Signature.Address[:]); err != nil || n != 20 {
return nil, fmt.Errorf("failed to read signature addr field: %w", err) v.Signatures = make([]*Signature, lenSignatures)
for i := 0; i < int(lenSignatures); i++ {
index, err := reader.ReadByte()
if err != nil {
return nil, fmt.Errorf("failed to read validator index [%d]", i)
}
signature := [65]byte{}
if n, err := reader.Read(signature[:]); err != nil || n != 65 {
return nil, fmt.Errorf("failed to read signature [%d]: %w", i, err)
}
v.Signatures[i] = &Signature{
Index: index,
Signature: signature,
}
} }
unixSeconds := uint32(0) unixSeconds := uint32(0)
@ -133,14 +145,15 @@ func ParseVAA(data []byte) (*VAA, error) {
} }
v.Timestamp = time.Unix(int64(unixSeconds), 0) v.Timestamp = time.Unix(int64(unixSeconds), 0)
action := data[61] currentPos := len(data) - reader.Len()
payloadLength := data[62] action := data[currentPos]
payloadLength := data[currentPos+1]
if len(data[63:]) != int(payloadLength) { if len(data[currentPos+2:]) != int(payloadLength) {
return nil, fmt.Errorf("payload length does not match given payload data size") return nil, fmt.Errorf("payload length does not match given payload data size")
} }
payloadReader := bytes.NewReader(data[63:]) payloadReader := bytes.NewReader(data[currentPos+2:])
var err error var err error
switch Action(action) { switch Action(action) {
case ActionGuardianSetUpdate: case ActionGuardianSetUpdate:
@ -175,46 +188,33 @@ func (v *VAA) SigningMsg() (*big.Int, error) {
} }
// VerifySignature verifies the signature of the VAA given a public key // VerifySignature verifies the signature of the VAA given a public key
func (v *VAA) VerifySignature(pubKey kyber.Point) bool { func (v *VAA) VerifySignatures(addresses []common.Address) bool {
if v.Signature == nil { if len(addresses) < len(v.Signatures) {
return false return false
} }
msg, err := v.SigningMsg() h, err := v.SigningMsg()
if err != nil { if err != nil {
return false return false
} }
sig := ethschnorr.NewSignature() for _, sig := range v.Signatures {
sig.Signature = new(big.Int).SetBytes(v.Signature.Sig[:]) if int(sig.Index) >= len(addresses) {
sig.CommitmentPublicAddress = v.Signature.Address return false
}
err = ethschnorr.Verify(pubKey, msg, sig) pubKey, err := crypto.Ecrecover(h.Bytes(), sig.Signature[:])
return err == nil if err != nil {
} return false
}
addr := common.BytesToAddress(crypto.Keccak256(pubKey[1:])[12:])
// Sign signs the VAA, setting it's signature field if addr != addresses[sig.Index] {
func (v *VAA) Sign(key *key.Pair) error { return false
if v.Signature != nil { }
return fmt.Errorf("VAA has already been signed")
} }
hash, err := v.SigningMsg() return true
if err != nil {
return fmt.Errorf("failed to get signing message: %w", err)
}
sig, err := ethschnorr.Sign(key.Private, hash)
if err != nil {
return fmt.Errorf("failed to sign: %w", err)
}
// Set fields
v.Signature = &Signature{}
copy(v.Signature.Sig[:], common.LeftPadBytes(sig.Signature.Bytes(), 32))
v.Signature.Address = sig.CommitmentPublicAddress
return nil
} }
// Serialize returns the binary representation of the VAA // Serialize returns the binary representation of the VAA
@ -223,14 +223,13 @@ func (v *VAA) Serialize() ([]byte, error) {
MustWrite(buf, binary.BigEndian, v.Version) MustWrite(buf, binary.BigEndian, v.Version)
MustWrite(buf, binary.BigEndian, v.GuardianSetIndex) MustWrite(buf, binary.BigEndian, v.GuardianSetIndex)
if v.Signature == nil { // Write signatures
return nil, fmt.Errorf("empty signature") MustWrite(buf, binary.BigEndian, uint8(len(v.Signatures)))
for _, sig := range v.Signatures {
MustWrite(buf, binary.BigEndian, sig.Index)
buf.Write(sig.Signature[:])
} }
// Write signature
buf.Write(v.Signature.Sig[:])
buf.Write(v.Signature.Address[:])
// Write Body // Write Body
body, err := v.serializeBody() body, err := v.serializeBody()
if err != nil { if err != nil {

View File

@ -1,10 +1,13 @@
package vaa package vaa
import ( import (
"crypto/ecdsa"
"crypto/rand"
"encoding/hex" "encoding/hex"
"github.com/certusone/wormhole/bridge/third_party/chainlink/cryptotest" "github.com/certusone/wormhole/bridge/third_party/chainlink/cryptotest"
"github.com/certusone/wormhole/bridge/third_party/chainlink/secp256k1" "github.com/certusone/wormhole/bridge/third_party/chainlink/secp256k1"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"math/big" "math/big"
"testing" "testing"
@ -23,9 +26,11 @@ func TestSerializeDeserialize(t *testing.T) {
vaa: &VAA{ vaa: &VAA{
Version: 1, Version: 1,
GuardianSetIndex: 9, GuardianSetIndex: 9,
Signature: &Signature{ Signatures: []*Signature{
Sig: [32]byte{2, 8}, {
Address: common.Address{1, 2, 3, 4}, Index: 1,
Signature: [65]byte{},
},
}, },
Timestamp: time.Unix(2837, 0), Timestamp: time.Unix(2837, 0),
Payload: &BodyTransfer{ Payload: &BodyTransfer{
@ -47,9 +52,11 @@ func TestSerializeDeserialize(t *testing.T) {
vaa: &VAA{ vaa: &VAA{
Version: 1, Version: 1,
GuardianSetIndex: 9, GuardianSetIndex: 9,
Signature: &Signature{ Signatures: []*Signature{
Sig: [32]byte{2, 8}, {
Address: common.Address{1, 2, 3, 4}, Index: 1,
Signature: [65]byte{},
},
}, },
Timestamp: time.Unix(2837, 0), Timestamp: time.Unix(2837, 0),
Payload: &BodyGuardianSetUpdate{ Payload: &BodyGuardianSetUpdate{
@ -75,8 +82,6 @@ func TestSerializeDeserialize(t *testing.T) {
} }
func TestVerifySignature(t *testing.T) { func TestVerifySignature(t *testing.T) {
key := secp256k1.Generate(randomStream)
v := &VAA{ v := &VAA{
Version: 8, Version: 8,
GuardianSetIndex: 9, GuardianSetIndex: 9,
@ -93,6 +98,23 @@ func TestVerifySignature(t *testing.T) {
}, },
} }
require.NoError(t, v.Sign(key)) data, err := v.SigningMsg()
require.True(t, v.VerifySignature(key.Public)) require.NoError(t, err)
key, err := ecdsa.GenerateKey(crypto.S256(), rand.Reader)
require.NoError(t, err)
sig, err := crypto.Sign(data.Bytes(), key)
require.NoError(t, err)
sigData := [65]byte{}
copy(sigData[:], sig)
v.Signatures = append(v.Signatures, &Signature{
Index: 0,
Signature: sigData,
})
addr := crypto.PubkeyToAddress(key.PublicKey)
require.True(t, v.VerifySignatures([]common.Address{
addr,
}))
} }