node: inject wormchain cosmwasm governance messages (#2057)
* node: inject wormchain cosmwasm governance messages * Use nested hash for defense in depth * Use keccak.Reset() instead of creating new hash objects * fix msg_server_wasmd_test * Updated based on jynnantonix comments * Check return value of binary.Write * Include actual error in binary.Write panic case
This commit is contained in:
parent
8ed35ddac1
commit
0e7c085d71
|
@ -162,6 +162,43 @@ func tokenBridgeUpgradeContract(req *nodev1.BridgeUpgradeContract, timestamp tim
|
|||
return v, nil
|
||||
}
|
||||
|
||||
// wormchainStoreCode converts a nodev1.WormchainStoreCode to its canonical VAA representation
|
||||
// Returns an error if the data is invalid
|
||||
func wormchainStoreCode(req *nodev1.WormchainStoreCode, timestamp time.Time, guardianSetIndex uint32, nonce uint32, sequence uint64) (*vaa.VAA, error) {
|
||||
// validate the length of the hex passed in
|
||||
b, err := hex.DecodeString(req.WasmHash)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid cosmwasm bytecode hash (expected hex): %w", err)
|
||||
}
|
||||
|
||||
if len(b) != 32 {
|
||||
return nil, fmt.Errorf("invalid cosmwasm bytecode hash (expected 32 bytes but received %d bytes)", len(b))
|
||||
}
|
||||
|
||||
wasmHash := [32]byte{}
|
||||
copy(wasmHash[:], b)
|
||||
|
||||
v := vaa.CreateGovernanceVAA(timestamp, nonce, sequence, guardianSetIndex,
|
||||
vaa.BodyWormchainStoreCode{
|
||||
WasmHash: wasmHash,
|
||||
}.Serialize())
|
||||
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// wormchainInstantiateContract converts a nodev1.WormchainInstantiateContract to its canonical VAA representation
|
||||
// Returns an error if the data is invalid
|
||||
func wormchainInstantiateContract(req *nodev1.WormchainInstantiateContract, timestamp time.Time, guardianSetIndex uint32, nonce uint32, sequence uint64) (*vaa.VAA, error) {
|
||||
instantiationParams_hash := vaa.CreateInstatiateCosmwasmContractHash(req.CodeId, req.Label, []byte(req.InstantiationMsg))
|
||||
|
||||
v := vaa.CreateGovernanceVAA(timestamp, nonce, sequence, guardianSetIndex,
|
||||
vaa.BodyWormchainInstantiateContract{
|
||||
InstantiationParamsHash: instantiationParams_hash,
|
||||
}.Serialize())
|
||||
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func (s *nodePrivilegedService) InjectGovernanceVAA(ctx context.Context, req *nodev1.InjectGovernanceVAARequest) (*nodev1.InjectGovernanceVAAResponse, error) {
|
||||
s.logger.Info("governance VAA injected via admin socket", zap.String("request", req.String()))
|
||||
|
||||
|
@ -184,6 +221,10 @@ func (s *nodePrivilegedService) InjectGovernanceVAA(ctx context.Context, req *no
|
|||
v, err = tokenBridgeRegisterChain(payload.BridgeRegisterChain, timestamp, req.CurrentSetIndex, message.Nonce, message.Sequence)
|
||||
case *nodev1.GovernanceMessage_BridgeContractUpgrade:
|
||||
v, err = tokenBridgeUpgradeContract(payload.BridgeContractUpgrade, timestamp, req.CurrentSetIndex, message.Nonce, message.Sequence)
|
||||
case *nodev1.GovernanceMessage_WormchainStoreCode:
|
||||
v, err = wormchainStoreCode(payload.WormchainStoreCode, timestamp, req.CurrentSetIndex, message.Nonce, message.Sequence)
|
||||
case *nodev1.GovernanceMessage_WormchainInstantiateContract:
|
||||
v, err = wormchainInstantiateContract(payload.WormchainInstantiateContract, timestamp, req.CurrentSetIndex, message.Nonce, message.Sequence)
|
||||
default:
|
||||
panic(fmt.Sprintf("unsupported VAA type: %T", payload))
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -80,6 +80,11 @@ message GovernanceMessage {
|
|||
|
||||
BridgeRegisterChain bridge_register_chain = 12;
|
||||
BridgeUpgradeContract bridge_contract_upgrade = 13;
|
||||
|
||||
// Wormchain
|
||||
|
||||
WormchainStoreCode wormchain_store_code = 14;
|
||||
WormchainInstantiateContract wormchain_instantiate_contract = 15;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -142,6 +147,22 @@ message BridgeUpgradeContract {
|
|||
string new_contract = 3;
|
||||
}
|
||||
|
||||
message WormchainStoreCode {
|
||||
// payload is the hex string of the sha3 256 hash of the wasm binary being uploaded
|
||||
string wasm_hash = 1;
|
||||
}
|
||||
|
||||
message WormchainInstantiateContract {
|
||||
// CodeID is the reference to the stored WASM code
|
||||
uint64 code_id = 1;
|
||||
|
||||
// Label is optional metadata to be stored with a contract instance.
|
||||
string label = 2;
|
||||
|
||||
// Json encoded message to be passed to the contract on instantiation
|
||||
string instantiation_msg = 3;
|
||||
}
|
||||
|
||||
message FindMissingMessagesRequest {
|
||||
// Emitter chain ID to iterate.
|
||||
uint32 emitter_chain = 1;
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
package vaa
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/sha3"
|
||||
)
|
||||
|
||||
var GovernanceEmitter = Address{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4}
|
||||
|
@ -23,3 +27,33 @@ func CreateGovernanceVAA(timestamp time.Time, nonce uint32, sequence uint64, gua
|
|||
|
||||
return vaa
|
||||
}
|
||||
|
||||
// Compute the hash for cosmwasm contract instatiation params.
|
||||
// The hash is keccak256 hash(hash(hash(BigEndian(CodeID)), Label), Msg).
|
||||
// We compute the nested hash so there is no chance of bytes leaking between CodeID, Label, and Msg.
|
||||
func CreateInstatiateCosmwasmContractHash(codeId uint64, label string, msg []byte) [32]byte {
|
||||
var expected_hash [32]byte
|
||||
|
||||
// hash(BigEndian(CodeID))
|
||||
var codeId_hash [32]byte
|
||||
keccak := sha3.NewLegacyKeccak256()
|
||||
if err := binary.Write(keccak, binary.BigEndian, codeId); err != nil {
|
||||
panic(fmt.Sprintf("failed to write binary data (%d): %v", codeId, err))
|
||||
}
|
||||
keccak.Sum(codeId_hash[:0])
|
||||
keccak.Reset()
|
||||
|
||||
// hash(hash(BigEndian(CodeID)), Label)
|
||||
var codeIdLabel_hash [32]byte
|
||||
keccak.Write(codeId_hash[:])
|
||||
keccak.Write([]byte(label))
|
||||
keccak.Sum(codeIdLabel_hash[:0])
|
||||
keccak.Reset()
|
||||
|
||||
// hash(hash(hash(BigEndian(CodeID)), Label), Msg)
|
||||
keccak.Write(codeIdLabel_hash[:])
|
||||
keccak.Write(msg)
|
||||
keccak.Sum(expected_hash[:0])
|
||||
|
||||
return expected_hash
|
||||
}
|
||||
|
|
|
@ -36,6 +36,16 @@ type (
|
|||
TargetChainID ChainID
|
||||
NewContract Address
|
||||
}
|
||||
|
||||
// BodyWormchainStoreCode is a governance message to upload a new cosmwasm contract to wormchain
|
||||
BodyWormchainStoreCode struct {
|
||||
WasmHash [32]byte
|
||||
}
|
||||
|
||||
// BodyWormchainInstantiateContract is a governance message to instantiate a cosmwasm contract on wormchain
|
||||
BodyWormchainInstantiateContract struct {
|
||||
InstantiationParamsHash [32]byte
|
||||
}
|
||||
)
|
||||
|
||||
func (b BodyContractUpgrade) Serialize() []byte {
|
||||
|
@ -73,47 +83,39 @@ func (b BodyGuardianSetUpdate) Serialize() []byte {
|
|||
}
|
||||
|
||||
func (r BodyTokenBridgeRegisterChain) Serialize() []byte {
|
||||
if len(r.Module) > 32 {
|
||||
panic("module longer than 32 byte")
|
||||
}
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
|
||||
// Write token bridge header
|
||||
for i := 0; i < (32 - len(r.Module)); i++ {
|
||||
buf.WriteByte(0x00)
|
||||
}
|
||||
buf.Write([]byte(r.Module))
|
||||
// Write action ID
|
||||
MustWrite(buf, binary.BigEndian, uint8(1))
|
||||
// Write target chain (0 = universal)
|
||||
MustWrite(buf, binary.BigEndian, uint16(0))
|
||||
// Write chain to be registered
|
||||
MustWrite(buf, binary.BigEndian, r.ChainID)
|
||||
// Write emitter address of chain to be registered
|
||||
buf.Write(r.EmitterAddress[:])
|
||||
|
||||
return buf.Bytes()
|
||||
return serializeBridgeGovernanceVaa(r.Module, 1, r.ChainID, r.EmitterAddress)
|
||||
}
|
||||
|
||||
func (r BodyTokenBridgeUpgradeContract) Serialize() []byte {
|
||||
if len(r.Module) > 32 {
|
||||
return serializeBridgeGovernanceVaa(r.Module, 2, r.TargetChainID, r.NewContract)
|
||||
}
|
||||
|
||||
func (r BodyWormchainStoreCode) Serialize() []byte {
|
||||
return serializeBridgeGovernanceVaa("WasmdModule", 1, ChainIDWormchain, r.WasmHash)
|
||||
}
|
||||
|
||||
func (r BodyWormchainInstantiateContract) Serialize() []byte {
|
||||
return serializeBridgeGovernanceVaa("WasmdModule", 2, ChainIDWormchain, r.InstantiationParamsHash)
|
||||
}
|
||||
|
||||
func serializeBridgeGovernanceVaa(module string, actionId uint8, chainId ChainID, payload [32]byte) []byte {
|
||||
if len(module) > 32 {
|
||||
panic("module longer than 32 byte")
|
||||
}
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
|
||||
// Write token bridge header
|
||||
for i := 0; i < (32 - len(r.Module)); i++ {
|
||||
for i := 0; i < (32 - len(module)); i++ {
|
||||
buf.WriteByte(0x00)
|
||||
}
|
||||
buf.Write([]byte(r.Module))
|
||||
buf.Write([]byte(module))
|
||||
// Write action ID
|
||||
MustWrite(buf, binary.BigEndian, uint8(2))
|
||||
MustWrite(buf, binary.BigEndian, actionId)
|
||||
// Write target chain
|
||||
MustWrite(buf, binary.BigEndian, r.TargetChainID)
|
||||
MustWrite(buf, binary.BigEndian, chainId)
|
||||
// Write emitter address of chain to be registered
|
||||
buf.Write(r.NewContract[:])
|
||||
buf.Write(payload[:])
|
||||
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
|
|
@ -3,12 +3,12 @@ package keeper
|
|||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/binary"
|
||||
|
||||
wasmdtypes "github.com/CosmWasm/wasmd/x/wasm/types"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
||||
"github.com/wormhole-foundation/wormchain/x/wormhole/types"
|
||||
"github.com/wormhole-foundation/wormhole/sdk/vaa"
|
||||
"golang.org/x/crypto/sha3"
|
||||
)
|
||||
|
||||
|
@ -92,16 +92,10 @@ func (k msgServer) InstantiateContract(goCtx context.Context, msg *types.MsgInst
|
|||
return nil, types.ErrUnknownGovernanceAction
|
||||
}
|
||||
|
||||
// Need to verify the msg contents by checking sha3.Sum(BigEndian(CodeID) || Label || Msg)
|
||||
// The vaa governance payload must contain this hash.
|
||||
|
||||
var expected_hash [32]byte
|
||||
keccak := sha3.NewLegacyKeccak256()
|
||||
binary.Write(keccak, binary.BigEndian, msg.CodeID)
|
||||
keccak.Write([]byte(msg.Label))
|
||||
keccak.Write([]byte(msg.Msg))
|
||||
keccak.Sum(expected_hash[:0])
|
||||
// Need to verify the instatiation arguments
|
||||
// The vaa governance payload must contain the hash of the expected args.
|
||||
|
||||
expected_hash := vaa.CreateInstatiateCosmwasmContractHash(msg.CodeID, msg.Label, msg.Msg)
|
||||
if !bytes.Equal(payload, expected_hash[:]) {
|
||||
return nil, types.ErrInvalidHash
|
||||
}
|
||||
|
|
|
@ -31,12 +31,7 @@ func createWasmInstantiatePayload(code_id uint64, label string, json_msg string)
|
|||
// - code_id (big endian)
|
||||
// - label
|
||||
// - json_msg
|
||||
var expected_hash [32]byte
|
||||
keccak := sha3.NewLegacyKeccak256()
|
||||
binary.Write(keccak, binary.BigEndian, code_id)
|
||||
keccak.Write([]byte(label))
|
||||
keccak.Write([]byte(json_msg))
|
||||
keccak.Sum(expected_hash[:0])
|
||||
expected_hash := vaa.CreateInstatiateCosmwasmContractHash(code_id, label, []byte(json_msg))
|
||||
|
||||
var payload bytes.Buffer
|
||||
payload.Write(keeper.WasmdModule[:])
|
||||
|
|
Loading…
Reference in New Issue