mirror of
https://github.com/luxfi/coreth.git
synced 2026-11-02 09:22:10 +00:00
This module would not build against the warp it resolves. warp v1.24 removed the
UnsignedMessage / Message split, and the rename is a rotation, not a substitution
— every name shifted one place along:
UnsignedMessage -> Message (the signed subject)
NewUnsignedMessage -> NewMessage
ParseUnsignedMessage -> ParseMessage
Message -> Envelope (subject + lanes)
NewMessage -> NewEnvelope
ParseMessage -> ParseEnvelope
VerifyMessage -> VerifyEnvelope
.UnsignedMessage -> .Message
.Signature -> .Beam
Applying it in the obvious order silently conflates the two Message meanings, so
the rewrite goes through placeholders.
The fold is a security fix upstream, not cosmetic: Message now carries the Pulsar
lineage (SourceNebulaRoot, SourceKeyEraID, SourceGeneration, HashSuiteID) inside
the signed subject. Under the old split the BLS Beam signed only the unsigned
body and that lineage was Beam-unauthenticated.
VerifyEnvelope takes the same five arguments VerifyMessage did and is documented
as the canonical classical path, so verification semantics are unchanged here.
VerifyWithOptions is the opt-in door to the Corona and ML-DSA lanes; nothing in
this module opts in, so no PQ policy is decided by this commit.
Two shape changes fall out of Envelope holding concrete lanes:
- Beam is a BitSetSignature value, not an interface, so the signer-count
type-assertion in the gas path is unreachable and its unknown-signature-type
branch is gone. A parsed envelope always has a Beam to count.
- Envelope.Bytes reports c14n failures instead of panicking, so the RPC path
surfaces that error rather than returning a truncated message.
NewEnvelope also takes the Corona and ML-DSA cert-set lanes; the test aggregator
is Beam-only and passes nil for both.
Builds clean, go vet clean, warp + precompile + aggregator suites pass.
Co-authored-by: Hanzo Dev <[email protected]>
101 lines
3.2 KiB
Go
101 lines
3.2 KiB
Go
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package aggregator
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/luxfi/crypto/bls"
|
|
"github.com/luxfi/ids"
|
|
"github.com/luxfi/warp"
|
|
)
|
|
|
|
const (
|
|
initialRetryFetchSignatureDelay = 100 * time.Millisecond
|
|
maxRetryFetchSignatureDelay = 5 * time.Second
|
|
retryBackoffFactor = 2
|
|
)
|
|
|
|
var _ SignatureGetter = (*NetworkSignatureGetter)(nil)
|
|
|
|
// SignatureGetter defines the minimum network interface to perform signature aggregation
|
|
type SignatureGetter interface {
|
|
// GetSignature attempts to fetch a BLS Signature from [nodeID] for [unsignedWarpMessage]
|
|
GetSignature(ctx context.Context, nodeID ids.NodeID, unsignedWarpMessage *warp.Message) (*bls.Signature, error)
|
|
}
|
|
|
|
type NetworkClient interface {
|
|
SendSyncedAppRequest(ctx context.Context, nodeID ids.NodeID, message []byte) ([]byte, error)
|
|
}
|
|
|
|
// NetworkSignatureGetter fetches warp signatures on behalf of the
|
|
// aggregator using VM App-Specific Messaging. Request/response framing
|
|
// is owned by the warp package (MarshalSignatureRequest /
|
|
// UnmarshalSignatureResponse) — no separate codec.Manager is required.
|
|
type NetworkSignatureGetter struct {
|
|
client NetworkClient
|
|
}
|
|
|
|
func NewSignatureGetter(client NetworkClient) *NetworkSignatureGetter {
|
|
return &NetworkSignatureGetter{
|
|
client: client,
|
|
}
|
|
}
|
|
|
|
// GetSignature attempts to fetch a BLS Signature of [unsignedWarpMessage] from [nodeID] until it succeeds or receives an invalid response
|
|
//
|
|
// Note: this function will continue attempting to fetch the signature from [nodeID] until it receives an invalid value or [ctx] is cancelled.
|
|
// The caller is responsible to cancel [ctx] if it no longer needs to fetch this signature.
|
|
func (s *NetworkSignatureGetter) GetSignature(ctx context.Context, nodeID ids.NodeID, unsignedWarpMessage *warp.Message) (*bls.Signature, error) {
|
|
req := &warp.SignatureRequest{Message: unsignedWarpMessage.Bytes()}
|
|
protoBytes, err := warp.MarshalSignatureRequest(req)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to marshal signature request: %w", err)
|
|
}
|
|
|
|
delay := initialRetryFetchSignatureDelay
|
|
timer := time.NewTimer(delay)
|
|
defer timer.Stop()
|
|
for {
|
|
signatureRes, err := s.client.SendSyncedAppRequest(ctx, nodeID, protoBytes)
|
|
// If the client fails to retrieve a response perform an exponential backoff.
|
|
// Note: it is up to the caller to ensure that [ctx] is eventually cancelled
|
|
if err != nil {
|
|
// Wait until the retry delay has elapsed before retrying.
|
|
if !timer.Stop() {
|
|
<-timer.C
|
|
}
|
|
timer.Reset(delay)
|
|
|
|
select {
|
|
case <-ctx.Done():
|
|
return nil, ctx.Err()
|
|
case <-timer.C:
|
|
}
|
|
|
|
// Exponential backoff.
|
|
delay *= retryBackoffFactor
|
|
if delay > maxRetryFetchSignatureDelay {
|
|
delay = maxRetryFetchSignatureDelay
|
|
}
|
|
continue
|
|
}
|
|
response, err := warp.UnmarshalSignatureResponse(signatureRes)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to unmarshal signature res: %w", err)
|
|
}
|
|
|
|
if len(response.Signature) == 0 {
|
|
return nil, fmt.Errorf("received empty signature response")
|
|
}
|
|
blsSignature, err := bls.SignatureFromBytes(response.Signature[:])
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to parse signature from res: %w", err)
|
|
}
|
|
return blsSignature, nil
|
|
}
|
|
}
|