mirror of
https://github.com/luxfi/chains.git
synced 2026-09-24 00:26:00 +00:00
warpmsg is the one place bridgevm, mpcvm and zkvm turn a payload into a signed cross-chain envelope. It copied whatever the signer returned into a fixed 96-byte array without checking the length. Signer is an interface, so that width is a runtime fact: a short signature was silently zero-padded and a long one silently truncated, and either way the envelope came out well-formed and unverifiable -- a message the sender believes it sent and no receiver can ever accept, with no error anywhere. oraclevm's accel host documented an error for a nil VM and never checked, deferring the failure to whichever method the operator called first.
66 lines
2.5 KiB
Go
66 lines
2.5 KiB
Go
// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
// Package warpmsg is the single, canonical place in chains that builds,
|
|
// signs, and wraps a single-signer Warp message.
|
|
//
|
|
// Every VM that emits a cross-chain Warp message — bridgevm reshare gossip,
|
|
// zkvm FHE task callbacks, mpcvm decryption fulfillments — calls
|
|
// BuildSigned. The build→sign→wrap sequence lives here and nowhere else, so
|
|
// there is exactly one way to turn (networkID, sourceChainID, payload) into a
|
|
// transmittable signed Warp envelope.
|
|
package warpmsg
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/luxfi/ids"
|
|
"github.com/luxfi/warp"
|
|
)
|
|
|
|
// BuildSigned builds a Warp content Message for (networkID, sourceChainID,
|
|
// payload), signs it with the node's BLS key over the Beam domain, and wraps
|
|
// the signature into a single-signer Envelope.
|
|
//
|
|
// The signer is the local node's warp.Signer (BLS); it signs
|
|
// BeamSigningBytes(message.ID()). The resulting signature is placed at
|
|
// validator bit index 0 — the single-signer convention every chains VM uses
|
|
// before the receiver aggregates and verifies the Beam BitSetSignature
|
|
// against the canonical validator set (warp.VerifyEnvelope). The returned
|
|
// Envelope is therefore the complete, transmittable, verifiable signed
|
|
// message: env.Bytes() for the wire, env.Message for the authenticated
|
|
// content.
|
|
func BuildSigned(signer warp.Signer, networkID uint32, sourceChainID ids.ID, payload []byte) (*warp.Envelope, error) {
|
|
msg, err := warp.NewMessage(networkID, sourceChainID, payload)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("build warp message: %w", err)
|
|
}
|
|
|
|
sigBytes, err := signer.Sign(msg)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("sign warp message: %w", err)
|
|
}
|
|
|
|
// Signer is an interface, so the width of what it returns is not a
|
|
// compile-time fact. Copying it into a fixed array without checking pads a
|
|
// short signature with zeros and silently truncates a long one, and either
|
|
// way the envelope is well-formed and unverifiable — a cross-chain message
|
|
// the sender believes it sent and no receiver can accept. Refuse instead.
|
|
if len(sigBytes) != warp.SignatureLen {
|
|
return nil, fmt.Errorf("sign warp message: signature is %d bytes, want %d",
|
|
len(sigBytes), warp.SignatureLen)
|
|
}
|
|
|
|
var sig [warp.SignatureLen]byte
|
|
copy(sig[:], sigBytes)
|
|
|
|
signers := warp.NewBitSet()
|
|
signers.Add(0)
|
|
|
|
env, err := warp.NewEnvelope(msg, warp.NewBitSetSignature(signers, sig), nil, nil)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("wrap warp envelope: %w", err)
|
|
}
|
|
return env, nil
|
|
}
|