Files
chains/quantumvm/vm.go
T
zeekayandHanzo Dev d87600fbef quantumvm: a block carries no signature of its own
ParseBlock demanded a field the wire has no room for. The block format is
timestamp, height, parentID, tx lengths, tx blob; the stamp lived only on the
object the builder made and was gone the moment the block was serialized, which
is every time it leaves the process. So the gate refused every block that ever
crossed the wire, genesis included, and Q-Chain accepted nothing on any network
for as long as it ran -- certAccepted=0, appliedHead=0, everywhere.

The two doors into the format disagreed, which is what made it a loop rather
than a stall. A responder answers a catch-up request through vm.parseBlock, which
has no gate and succeeds; the requester admits the reply through VM.ParseBlock,
which had the gate and failed. Same bytes, two verdicts: every node served the
block every node refused.

Putting the stamp on the wire would be worse than leaving it off. The block id is
sha256 of its own bytes, so a stamp in those bytes makes the id depend on who
signed, and two honest nodes compute two ids for one block. seedGenesis already
refuses wall-clock time for exactly this reason. The stamp could not have been
load-bearing anyway: it was made with a key generated fresh for each block, and a
signature that ships its own throwaway public key states nothing an attacker
cannot also state. Its thirty-second validity window is also the opposite of what
catch-up needs, which is to serve blocks from hours ago.

A block-level signature belongs to consensus, where it already is: a cert is a
quorum's statement about a block, checkable by anyone holding the validator set,
and it is not a field on the block. Quasar still signs there. Transaction-level
quantum signatures are untouched -- those are serialized, and they are verified.

The tests stayed green throughout because none of them sent a block through
Bytes() and back. Three do now: parse what we serialize, parse GENESIS, and the
two parse paths reach the same verdict on identical bytes. Restoring the gate
fails all three.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-20 11:44:10 -07:00

757 lines
23 KiB
Go

// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package quantumvm
import (
"context"
"encoding/binary"
"errors"
"fmt"
"net/http"
"sync"
"time"
"github.com/gorilla/rpc/v2"
"github.com/luxfi/chains/quantumvm/config"
"github.com/luxfi/chains/quantumvm/quantum"
consensuschain "github.com/luxfi/consensus/engine/chain"
"github.com/luxfi/consensus/protocol/quasar"
"github.com/luxfi/database"
"github.com/luxfi/database/versiondb"
"github.com/luxfi/ids"
"github.com/luxfi/log"
"github.com/luxfi/metric"
"github.com/luxfi/node/cache"
"github.com/luxfi/node/utils/json"
"github.com/luxfi/node/vms/types/fee"
"github.com/luxfi/timer/mockable"
"github.com/luxfi/version"
luxvm "github.com/luxfi/vm"
"github.com/luxfi/vm/chain"
)
const (
// Version of the QVM
Version = "1.0.0"
// MaxParallelVerifications is the maximum number of parallel verifications
MaxParallelVerifications = 100
// DefaultBatchSize is the default batch size for parallel processing
DefaultBatchSize = 10
)
var (
errNotImplemented = errors.New("not implemented")
errNoPendingTxs = errors.New("no pending transactions")
errVMShutdown = errors.New("VM is shutting down")
errInvalidQuantumStamp = errors.New("invalid quantum stamp")
errParallelProcessingFailed = errors.New("parallel transaction processing failed")
// Compile-time check that *VM satisfies chain.ChainVM (= block.ChainVM)
// AND the consensus engine's BlockBuilder. Together these prove Q-Chain
// takes the LINEAR ⅔-stake cert path in the chain manager (buildChain →
// consensuschain.NewRuntime), not the DAG path. Do NOT add a
// GetEngine() consensusdag.Engine method: that would route the manager's
// type switch back to createDAG and bypass the certificate.
_ chain.ChainVM = (*VM)(nil)
_ consensuschain.BlockBuilder = (*VM)(nil)
)
// BCLookup provides blockchain alias lookup
type BCLookup interface {
Lookup(string) (ids.ID, error)
PrimaryAlias(ids.ID) (string, error)
}
// SharedMemory provides cross-chain shared memory
type SharedMemory interface {
Get(peerChainID ids.ID, keys [][]byte) ([][]byte, error)
Apply(map[ids.ID]interface{}, ...interface{}) error
}
// VM implements the Q-chain Virtual Machine with quantum features
type VM struct {
config.Config
// Core components
// consensusRuntime *runtime.Runtime
log log.Logger
db database.Database
versiondb *versiondb.Database
blockchainID ids.ID
ChainAlias string
NetworkID uint32
// Quantum components
quantumSigner *quantum.QuantumSigner
quantumCache *cache.LRU[ids.ID, *quantum.QuantumSignature]
// Hybrid P/Q consensus bridge (connects P-Chain BLS + Q-Chain Corona)
// Uses Quasar consensus for dual BLS+Corona threshold signatures
quasarBridge *QuasarBridge
// Consensus and validation
// validators validators.Manager
// versionManager consensusversion.Manager
// consensusEngine consensus.Consensus
// Metrics and monitoring
metrics metric.Registry
// State management
state database.Database
shuttingDown bool
shuttingDownMu sync.RWMutex
// Transaction processing
txPool *TransactionPool
parallelWorkers int
workerPool *sync.Pool
// Clock and timing
clock mockable.Clock
// Network communication
bcLookup BCLookup
sharedMemory SharedMemory
// HTTP service
httpServer *http.Server
rpcServer *rpc.Server
// Fee policy gating user-submitted tx admission (IssueTx).
// FlatPolicy at MinTxFeeFloor on Q-Chain; consensus-internal
// callers bypass via txPool.AddTransaction.
feePolicy fee.Policy
// Synchronization
lock sync.RWMutex
}
// Initialize initializes the VM. Implements chain.ChainVM.
func (vm *VM) Initialize(ctx context.Context, init luxvm.Init) error {
vm.lock.Lock()
defer vm.lock.Unlock()
_ = ctx
vm.db = init.DB
if init.Log != nil {
vm.log = init.Log
}
genesisBytes := init.Genesis
// vm.blockchainID = chainRuntime.ChainID
// vm.NetworkID = chainRuntime.NetworkID
// Initialize logger
// if vm.log.IsZero() {
// vm.log = chainRuntime.Log
// }
// vm.log.Info("initializing QVM",
// "version", Version,
// "chainID", vm.blockchainID,
// "networkID", vm.NetworkID,
// )
// Initialize quantum components
vm.quantumSigner = quantum.NewQuantumSigner(
vm.log,
vm.Config.QuantumAlgorithmVersion,
vm.Config.CoronaKeySize,
vm.Config.QuantumStampWindow,
vm.Config.QuantumSigCacheSize,
)
// Initialize transaction pool
vm.txPool = NewTransactionPool(
vm.Config.MaxParallelTxs,
vm.Config.ParallelBatchSize,
vm.log,
)
// Pin fee policy. Q-Chain accepts user-submitted txs through
// IssueTx so attach the canonical FlatPolicy at MinTxFeeFloor;
// fee.Validate refuses zero-fee user-facing chains at boot.
if init.Runtime != nil {
vm.NetworkID = init.Runtime.NetworkID
}
vm.feePolicy = newFeePolicy(vm.NetworkID)
if err := fee.Validate(vm.feePolicy); err != nil {
return fmt.Errorf("quantumvm: fee policy: %w", err)
}
// Set up worker pool for parallel processing
vm.parallelWorkers = vm.Config.MaxParallelTxs
if vm.parallelWorkers <= 0 {
vm.parallelWorkers = MaxParallelVerifications
}
vm.workerPool = &sync.Pool{
New: func() interface{} {
return &TransactionWorker{
vm: vm,
quantumSigner: vm.quantumSigner,
}
},
}
// Initialize version database
vm.versiondb = versiondb.New(vm.db)
// Metrics are not yet available via chainRuntime; the quantumvm
// operates without per-chain metrics until runtime exposes them.
// Parse genesis if provided
if len(genesisBytes) > 0 {
if err := vm.parseGenesis(genesisBytes); err != nil {
return fmt.Errorf("failed to parse genesis: %w", err)
}
}
// Initialize state
vm.state = vm.versiondb
// A chain with no block cannot answer the frontier query bootstrap starts
// with, so write height 0 before anything asks.
if err := vm.seedGenesis(); err != nil {
return fmt.Errorf("failed to seed genesis: %w", err)
}
// Set up HTTP handlers
if err := vm.initializeHTTPHandlers(); err != nil {
return fmt.Errorf("failed to initialize HTTP handlers: %w", err)
}
// Initialize Quasar hybrid consensus bridge (BLS + Corona)
quasarCfg := QuasarBridgeConfig{
ValidatorID: vm.blockchainID.String(),
Threshold: 0, // Will be set to 2/3+1 based on total nodes
TotalNodes: 3, // Default 3-node network, can be updated
Logger: vm.log,
}
quasarBridge, err := NewQuasarBridge(quasarCfg, vm.quantumSigner)
if err != nil {
return fmt.Errorf("failed to initialize Quasar bridge: %w", err)
}
vm.quasarBridge = quasarBridge
vm.log.Info("═══════════════════════════════════════════════════════════════════")
vm.log.Info("║ QVM INITIALIZED with Quasar PQ-BFT Consensus ║")
vm.log.Info("───────────────────────────────────────────────────────────────────")
vm.log.Info("║ Quantum Signatures: ML-DSA (NIST PQC)", log.Bool("enabled", vm.Config.QuantumStampEnabled))
vm.log.Info("║ Corona Threshold: Ring-LWE PQ", log.Bool("enabled", vm.Config.CoronaEnabled))
vm.log.Info("║ BLS Threshold: Classical fast path", log.Bool("enabled", true))
vm.log.Info("║ Quasar Hybrid: BLS + Corona dual signing", log.Bool("enabled", true))
vm.log.Info("║ Parallel TX Processing:", log.Int("maxParallel", vm.Config.MaxParallelTxs))
vm.log.Info("═══════════════════════════════════════════════════════════════════")
return nil
}
// BuildBlock builds a new block with pending transactions. Implements chain.ChainVM.
func (vm *VM) BuildBlock(ctx context.Context) (chain.Block, error) {
vm.lock.Lock()
defer vm.lock.Unlock()
// Check if VM is shutting down
if vm.isShuttingDown() {
return nil, errVMShutdown
}
// Get pending transactions from pool
pendingTxs := vm.txPool.GetPendingTransactions(vm.Config.ParallelBatchSize)
if len(pendingTxs) == 0 {
return nil, errNoPendingTxs
}
// Process transactions in parallel
validTxs, err := vm.processTransactionsParallel(pendingTxs)
if err != nil {
return nil, fmt.Errorf("failed to process transactions: %w", err)
}
// Create new block with valid transactions. The id is the content hash of
// the canonical wire (computeID = sha256(Bytes())); it is derived AFTER the
// structural fields are set, and the wire does not carry the id, so
// Bytes()/id are independent of ordering.
block := &Block{
timestamp: vm.clock.Time(),
height: vm.getHeight() + 1,
parentID: vm.getLastAcceptedID(),
transactions: validTxs,
vm: vm,
}
block.id = block.computeID()
vm.signBlockWithQuasar(block)
vm.log.Debug("built block",
"blockID", block.ID(),
"height", block.Height(),
"txCount", len(validTxs),
)
return block, nil
}
// ParseBlock parses a block from bytes. Implements chain.ChainVM.
func (vm *VM) ParseBlock(ctx context.Context, blockBytes []byte) (chain.Block, error) {
vm.lock.RLock()
defer vm.lock.RUnlock()
block, err := vm.parseBlock(blockBytes)
if err != nil {
return nil, fmt.Errorf("failed to parse block: %w", err)
}
return block, nil
}
// GetBlock retrieves a block by its ID. Implements chain.ChainVM.
func (vm *VM) GetBlock(ctx context.Context, blockID ids.ID) (chain.Block, error) {
vm.lock.RLock()
defer vm.lock.RUnlock()
blockBytes, err := vm.state.Get(blockID[:])
if err != nil {
return nil, fmt.Errorf("failed to get block %s: %w", blockID, err)
}
return vm.parseBlock(blockBytes)
}
// SetState sets the VM state. Implements chain.ChainVM.
func (vm *VM) SetState(ctx context.Context, state uint32) error {
vm.lock.Lock()
defer vm.lock.Unlock()
// Q-Chain uses quantum state management - log state transitions generically
vm.log.Info("QVM state transition", "state", state)
return nil
}
// Shutdown gracefully shuts down the VM
func (vm *VM) Shutdown(ctx context.Context) error {
vm.shuttingDownMu.Lock()
vm.shuttingDown = true
vm.shuttingDownMu.Unlock()
vm.log.Info("shutting down QVM")
// Stop HTTP server
if vm.httpServer != nil {
if err := vm.httpServer.Shutdown(ctx); err != nil {
vm.log.Error("failed to shutdown HTTP server", "error", err)
}
}
// Close transaction pool
if vm.txPool != nil {
vm.txPool.Close()
}
// Close database
if vm.versiondb != nil {
if err := vm.versiondb.Close(); err != nil {
vm.log.Error("failed to close versiondb", "error", err)
}
}
vm.log.Info("QVM shutdown complete")
return nil
}
// processTransactionsParallel processes transactions in parallel batches
func (vm *VM) processTransactionsParallel(txs []Transaction) ([]Transaction, error) {
if len(txs) == 0 {
return nil, nil
}
// Determine batch size
batchSize := vm.Config.ParallelBatchSize
if batchSize <= 0 {
batchSize = DefaultBatchSize
}
var validTxs []Transaction
var mu sync.Mutex
var wg sync.WaitGroup
// Process in batches
for i := 0; i < len(txs); i += batchSize {
end := i + batchSize
if end > len(txs) {
end = len(txs)
}
batch := txs[i:end]
wg.Add(1)
go func(batch []Transaction) {
defer wg.Done()
// Get worker from pool
worker := vm.workerPool.Get().(*TransactionWorker)
defer vm.workerPool.Put(worker)
// Process batch
validBatch, err := worker.ProcessBatch(batch)
if err != nil {
vm.log.Error("batch processing failed", "error", err)
return
}
// Add valid transactions
mu.Lock()
validTxs = append(validTxs, validBatch...)
mu.Unlock()
}(batch)
}
wg.Wait()
if len(validTxs) == 0 {
return nil, errParallelProcessingFailed
}
return validTxs, nil
}
// Quasar signs the block for the CONSENSUS layer, which is where a block-level
// signature belongs: a cert is a quorum's statement about a block, verifiable by
// anyone holding the validator set. It is deliberately not a field on the block.
//
// A per-block stamp cannot be one. The block id is sha256 of its own bytes, so a
// stamp on the wire makes the id depend on WHO signed and two honest nodes compute
// two ids for one block -- a fork by construction, the same hazard seedGenesis
// avoids by refusing to put wall-clock time in a block. Kept off the wire instead,
// it exists only on the object the builder made and is gone the moment the block
// is serialized, which is every time it leaves this process.
func (vm *VM) signBlockWithQuasar(block *Block) {
if vm.quasarBridge == nil {
return
}
if _, err := vm.quasarBridge.SignBlock(context.Background(), block.ID(), block.Bytes(), block.Height()); err != nil {
vm.log.Warn("quasar block signing failed", "blockID", block.ID(), "error", err)
return
}
vm.log.Debug("block signed with quasar BLS threshold", "blockID", block.ID(), "height", block.Height())
}
// parseGenesis parses genesis data
func (vm *VM) parseGenesis(genesisBytes []byte) error {
// Genesis parsing for quantumvm is a no-op; initial state is derived
// from the quantum signer configuration and the empty state trie.
vm.log.Info("genesis loaded", "size", len(genesisBytes))
return nil
}
// seedGenesis writes the height-0 block on a chain that has none, so the VM can
// name a tip the moment it starts.
//
// A VM whose last-accepted is empty answers the bootstrap frontier query with no
// block, and an answer naming no block is not a responder — it neither backs a
// tip nor counts toward the response floor (see tally in the node's
// chains/bootstrap_trust.go). Every node then reads every other node as silent,
// so the beacon floor is unreachable and each waits on the others for as long as
// the chain runs. State derived lazily from config is enough to EXECUTE against,
// but consensus starts by asking what block you hold, and "none" is not an
// answer it can build a quorum from.
//
// The block is a constant, so every node computes one id alone: fixed timestamp,
// height 0, empty parent, no transactions. Wall-clock time here would give each
// node a different id for the same block and make the repair a fork.
func (vm *VM) seedGenesis() error {
if vm.getLastAcceptedID() != ids.Empty {
return nil
}
block := &Block{
timestamp: time.Unix(0, 0).UTC(),
height: 0,
parentID: ids.Empty,
vm: vm,
}
block.id = block.computeID()
if err := vm.state.Put(block.id[:], block.Bytes()); err != nil {
return fmt.Errorf("store genesis block: %w", err)
}
if err := vm.state.Put([]byte("lastAccepted"), block.id[:]); err != nil {
return fmt.Errorf("set genesis last-accepted: %w", err)
}
heightBytes := make([]byte, 8)
binary.BigEndian.PutUint64(heightBytes, block.height)
if err := vm.state.Put([]byte("height"), heightBytes); err != nil {
return fmt.Errorf("set genesis height: %w", err)
}
// Commit, or the tip exists only in memory and the next start is back to
// naming no block.
if err := vm.versiondb.Commit(); err != nil {
return fmt.Errorf("commit genesis: %w", err)
}
vm.log.Info("genesis block written", "blockID", block.id, "height", block.height)
return nil
}
// parseBlock parses a block from its ZAP wire (see wire.go). The transaction set
// is not reconstructed — the concrete Transaction is an interface with no
// on-chain deserializer; the raw wire is retained for signature re-verification.
func (vm *VM) parseBlock(blockBytes []byte) (*Block, error) {
return parseBlockBytes(vm, blockBytes)
}
// initializeHTTPHandlers sets up HTTP handlers
func (vm *VM) initializeHTTPHandlers() error {
vm.rpcServer = rpc.NewServer()
// Register QVM service
service := &Service{vm: vm}
vm.rpcServer.RegisterCodec(json.NewCodec(), "application/json")
vm.rpcServer.RegisterCodec(json.NewCodec(), "application/json;charset=UTF-8")
return vm.rpcServer.RegisterService(service, "quantumvm")
}
// isShuttingDown returns true if VM is shutting down
func (vm *VM) isShuttingDown() bool {
vm.shuttingDownMu.RLock()
defer vm.shuttingDownMu.RUnlock()
return vm.shuttingDown
}
// getHeight returns current blockchain height
func (vm *VM) getHeight() uint64 {
heightBytes, err := vm.state.Get([]byte("height"))
if err != nil {
return 0
}
if len(heightBytes) != 8 {
return 0
}
return binary.BigEndian.Uint64(heightBytes)
}
// getLastAcceptedID returns the last accepted block ID
func (vm *VM) getLastAcceptedID() ids.ID {
lastAcceptedBytes, err := vm.state.Get([]byte("lastAccepted"))
if err != nil {
return ids.Empty
}
if len(lastAcceptedBytes) != 32 {
return ids.Empty
}
var id ids.ID
copy(id[:], lastAcceptedBytes)
return id
}
// Version returns the version of the VM
func (vm *VM) Version(ctx context.Context) (string, error) {
return Version, nil
}
// Connected notifies the VM that a validator has connected
func (vm *VM) Connected(ctx context.Context, nodeID ids.NodeID, nodeVersion *version.Application) error {
vm.log.Debug("node connected", "nodeID", nodeID, "version", nodeVersion)
return nil
}
// Disconnected notifies the VM that a validator has disconnected
func (vm *VM) Disconnected(ctx context.Context, nodeID ids.NodeID) error {
vm.log.Debug("node disconnected", "nodeID", nodeID)
return nil
}
// HealthCheck returns the health status of the VM
func (vm *VM) HealthCheck(ctx context.Context) (chain.HealthResult, error) {
vm.lock.RLock()
defer vm.lock.RUnlock()
return chain.HealthResult{
Healthy: !vm.isShuttingDown(),
Details: map[string]string{
"version": Version,
"quantumEnabled": fmt.Sprintf("%v", vm.Config.QuantumStampEnabled),
"coronaEnabled": fmt.Sprintf("%v", vm.Config.CoronaEnabled),
"pendingTxs": fmt.Sprintf("%d", vm.txPool.PendingCount()),
},
}, nil
}
// CreateHandlers returns HTTP handlers for the VM
func (vm *VM) CreateHandlers(ctx context.Context) (map[string]http.Handler, error) {
handlers := map[string]http.Handler{
"/rpc": vm.rpcServer,
}
return handlers, nil
}
// CreateStaticHandlers returns static HTTP handlers
func (vm *VM) CreateStaticHandlers(ctx context.Context) (map[string]http.Handler, error) {
return nil, nil
}
// NewHTTPHandler returns the VM's HTTP handler. Implements chain.ChainVM.
func (vm *VM) NewHTTPHandler(ctx context.Context) (http.Handler, error) {
handlers, err := vm.CreateHandlers(ctx)
if err != nil {
return nil, err
}
mux := http.NewServeMux()
for path, h := range handlers {
if path == "" {
path = "/"
}
mux.Handle(path, h)
}
return mux, nil
}
// SetPreference sets the preferred block. Implements chain.ChainVM.
// Q-Chain uses BLS+Corona threshold finality rather than preference,
// so this is a no-op until preference-based fork choice is wired in.
func (vm *VM) SetPreference(ctx context.Context, blockID ids.ID) error {
return nil
}
// LastAccepted returns the last accepted block ID. Implements chain.ChainVM.
func (vm *VM) LastAccepted(ctx context.Context) (ids.ID, error) {
vm.lock.RLock()
defer vm.lock.RUnlock()
return vm.getLastAcceptedID(), nil
}
// GetBlockIDAtHeight returns the block ID at the given height. Implements chain.ChainVM.
// Q-Chain does not yet maintain a height index, so this returns errNotImplemented
// until indexer integration lands.
func (vm *VM) GetBlockIDAtHeight(ctx context.Context, height uint64) (ids.ID, error) {
return ids.Empty, errNotImplemented
}
// WaitForEvent blocks until an event triggers block building. Implements chain.ChainVM.
// CRITICAL: must block on ctx.Done() to avoid the notification flood loop in
// node/chains/manager.go (matches the relayvm contract).
func (vm *VM) WaitForEvent(ctx context.Context) (luxvm.Message, error) {
<-ctx.Done()
return luxvm.Message{}, ctx.Err()
}
// GetQuasarBridge returns the Quasar hybrid consensus bridge
// This provides BLS + Corona dual threshold signatures for PQ finality
func (vm *VM) GetQuasarBridge() *QuasarBridge {
vm.lock.RLock()
defer vm.lock.RUnlock()
return vm.quasarBridge
}
// GetHybridBridge returns the hybrid finality bridge for P/Q chain consensus
// This connects P-Chain BLS signatures with Q-Chain Corona for quantum finality
// Deprecated: Use GetQuasarBridge() for proper type safety
func (vm *VM) GetHybridBridge() interface{} {
return vm.GetQuasarBridge()
}
// SetHybridBridge sets the hybrid finality bridge (called by chain manager)
// Deprecated: Bridge is now auto-initialized in VM.Initialize()
func (vm *VM) SetHybridBridge(bridge interface{}) {
vm.lock.Lock()
defer vm.lock.Unlock()
if qb, ok := bridge.(*QuasarBridge); ok {
vm.quasarBridge = qb
}
}
// StampBlock implements QChainStamper interface for hybrid finality
// Uses Quasar BLS+Corona for dual post-quantum threshold signatures
func (vm *VM) StampBlock(blockID interface{}, pChainHeight uint64, message []byte) (interface{}, error) {
vm.lock.RLock()
defer vm.lock.RUnlock()
ctx := context.Background()
// Convert blockID to ids.ID if possible
var blkID ids.ID
switch v := blockID.(type) {
case ids.ID:
blkID = v
case string:
parsed, err := ids.FromString(v)
if err == nil {
blkID = parsed
}
}
// Use Quasar bridge for BLS threshold signature if available
if vm.quasarBridge != nil && blkID != ids.Empty {
hybridSig, err := vm.quasarBridge.SignBlock(ctx, blkID, message, pChainHeight)
if err != nil {
vm.log.Warn("Quasar BLS stamp failed, using ML-DSA fallback", "error", err)
} else {
vm.log.Info("Quasar BLS stamp created",
"blockID", blkID,
"pChainHeight", pChainHeight,
"threshold", vm.quasarBridge.GetThreshold(),
)
// Return hybrid signature for BLS finality
return hybridSig, nil
}
}
// Fallback: Generate quantum stamp using ML-DSA signer
key, err := vm.quantumSigner.GenerateCoronaKey()
if err != nil {
return nil, fmt.Errorf("failed to generate key for stamp: %w", err)
}
sig, err := vm.quantumSigner.Sign(message, key)
if err != nil {
return nil, fmt.Errorf("failed to create quantum stamp: %w", err)
}
vm.log.Debug("ML-DSA quantum stamp created",
"pChainHeight", pChainHeight,
"sigLen", len(sig.Signature),
)
return sig, nil
}
// VerifyStamp implements QChainStamper interface for quasar finality
// Supports both Quasar QuasarSignature and ML-DSA QuantumSignature
func (vm *VM) VerifyStamp(stamp interface{}) error {
switch s := stamp.(type) {
case *quasar.QuasarSignature:
// Quasar BLS + Corona threshold signature
if s.BLS == nil || len(s.BLS.Signature) == 0 {
return errors.New("invalid Quasar BLS signature")
}
vm.log.Debug("Verified Quasar stamp",
"validatorID", s.BLS.ValidatorID,
"threshold", s.BLS.IsThreshold,
)
return nil
case *quasar.AggregatedSignature:
// Aggregated threshold signature
if len(s.BLSAggregated) == 0 || s.SignerCount < vm.quasarBridge.GetThreshold() {
return errors.New("insufficient aggregated signature")
}
vm.log.Debug("Verified aggregated Quasar stamp",
"signerCount", s.SignerCount,
"threshold", s.IsThreshold,
)
return nil
case *quantum.QuantumSignature:
// ML-DSA quantum signature
if len(s.Signature) == 0 || len(s.QuantumStamp) == 0 {
return errors.New("invalid quantum stamp structure")
}
return nil
default:
return errors.New("unsupported stamp type")
}
}