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
|
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) {
|
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()))
|
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)
|
v, err = tokenBridgeRegisterChain(payload.BridgeRegisterChain, timestamp, req.CurrentSetIndex, message.Nonce, message.Sequence)
|
||||||
case *nodev1.GovernanceMessage_BridgeContractUpgrade:
|
case *nodev1.GovernanceMessage_BridgeContractUpgrade:
|
||||||
v, err = tokenBridgeUpgradeContract(payload.BridgeContractUpgrade, timestamp, req.CurrentSetIndex, message.Nonce, message.Sequence)
|
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:
|
default:
|
||||||
panic(fmt.Sprintf("unsupported VAA type: %T", payload))
|
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;
|
BridgeRegisterChain bridge_register_chain = 12;
|
||||||
BridgeUpgradeContract bridge_contract_upgrade = 13;
|
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;
|
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 {
|
message FindMissingMessagesRequest {
|
||||||
// Emitter chain ID to iterate.
|
// Emitter chain ID to iterate.
|
||||||
uint32 emitter_chain = 1;
|
uint32 emitter_chain = 1;
|
||||||
|
|
|
@ -1,7 +1,11 @@
|
||||||
package vaa
|
package vaa
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
"time"
|
"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}
|
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
|
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
|
TargetChainID ChainID
|
||||||
NewContract Address
|
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 {
|
func (b BodyContractUpgrade) Serialize() []byte {
|
||||||
|
@ -73,47 +83,39 @@ func (b BodyGuardianSetUpdate) Serialize() []byte {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r BodyTokenBridgeRegisterChain) Serialize() []byte {
|
func (r BodyTokenBridgeRegisterChain) Serialize() []byte {
|
||||||
if len(r.Module) > 32 {
|
return serializeBridgeGovernanceVaa(r.Module, 1, r.ChainID, r.EmitterAddress)
|
||||||
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()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r BodyTokenBridgeUpgradeContract) Serialize() []byte {
|
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")
|
panic("module longer than 32 byte")
|
||||||
}
|
}
|
||||||
|
|
||||||
buf := &bytes.Buffer{}
|
buf := &bytes.Buffer{}
|
||||||
|
|
||||||
// Write token bridge header
|
// 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.WriteByte(0x00)
|
||||||
}
|
}
|
||||||
buf.Write([]byte(r.Module))
|
buf.Write([]byte(module))
|
||||||
// Write action ID
|
// Write action ID
|
||||||
MustWrite(buf, binary.BigEndian, uint8(2))
|
MustWrite(buf, binary.BigEndian, actionId)
|
||||||
// Write target chain
|
// Write target chain
|
||||||
MustWrite(buf, binary.BigEndian, r.TargetChainID)
|
MustWrite(buf, binary.BigEndian, chainId)
|
||||||
// Write emitter address of chain to be registered
|
// Write emitter address of chain to be registered
|
||||||
buf.Write(r.NewContract[:])
|
buf.Write(payload[:])
|
||||||
|
|
||||||
return buf.Bytes()
|
return buf.Bytes()
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,12 +3,12 @@ package keeper
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"encoding/binary"
|
|
||||||
|
|
||||||
wasmdtypes "github.com/CosmWasm/wasmd/x/wasm/types"
|
wasmdtypes "github.com/CosmWasm/wasmd/x/wasm/types"
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
||||||
"github.com/wormhole-foundation/wormchain/x/wormhole/types"
|
"github.com/wormhole-foundation/wormchain/x/wormhole/types"
|
||||||
|
"github.com/wormhole-foundation/wormhole/sdk/vaa"
|
||||||
"golang.org/x/crypto/sha3"
|
"golang.org/x/crypto/sha3"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -92,16 +92,10 @@ func (k msgServer) InstantiateContract(goCtx context.Context, msg *types.MsgInst
|
||||||
return nil, types.ErrUnknownGovernanceAction
|
return nil, types.ErrUnknownGovernanceAction
|
||||||
}
|
}
|
||||||
|
|
||||||
// Need to verify the msg contents by checking sha3.Sum(BigEndian(CodeID) || Label || Msg)
|
// Need to verify the instatiation arguments
|
||||||
// The vaa governance payload must contain this hash.
|
// The vaa governance payload must contain the hash of the expected args.
|
||||||
|
|
||||||
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])
|
|
||||||
|
|
||||||
|
expected_hash := vaa.CreateInstatiateCosmwasmContractHash(msg.CodeID, msg.Label, msg.Msg)
|
||||||
if !bytes.Equal(payload, expected_hash[:]) {
|
if !bytes.Equal(payload, expected_hash[:]) {
|
||||||
return nil, types.ErrInvalidHash
|
return nil, types.ErrInvalidHash
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,12 +31,7 @@ func createWasmInstantiatePayload(code_id uint64, label string, json_msg string)
|
||||||
// - code_id (big endian)
|
// - code_id (big endian)
|
||||||
// - label
|
// - label
|
||||||
// - json_msg
|
// - json_msg
|
||||||
var expected_hash [32]byte
|
expected_hash := vaa.CreateInstatiateCosmwasmContractHash(code_id, label, []byte(json_msg))
|
||||||
keccak := sha3.NewLegacyKeccak256()
|
|
||||||
binary.Write(keccak, binary.BigEndian, code_id)
|
|
||||||
keccak.Write([]byte(label))
|
|
||||||
keccak.Write([]byte(json_msg))
|
|
||||||
keccak.Sum(expected_hash[:0])
|
|
||||||
|
|
||||||
var payload bytes.Buffer
|
var payload bytes.Buffer
|
||||||
payload.Write(keeper.WasmdModule[:])
|
payload.Write(keeper.WasmdModule[:])
|
||||||
|
|
Loading…
Reference in New Issue