mirror of
https://github.com/luxfi/sdk.git
synced 2026-10-30 15:19:23 +00:00
warp split content from signatures: a Message is content, an Envelope is that content plus the beam, corona and ML-DSA signatures over it. Every function here was still named Get...Message and typed *warp.Message regardless of which it produced, so one word did two jobs and the compiler could not tell them apart. Each signature is now set from what the function actually returns, not from its name. SignMessage parses what the aggregator hands back — a message plus the signatures gathered over it — so it returns an Envelope. The three functions that aggregate return Envelope; the ones that construct or extract content return Message; and where a caller wants the content out of a signed result it takes .Message rather than the wrapper. Two functions in registration.go look like they should follow but do not: they aggregate and then convert the result back to a node warp message, so a Message is genuinely what they yield. go build ./... exits 0 and go test ./... is 23 ok. The three packages that still fail to build their tests -- profiles, wallet/keychain, wallet/primary -- fail identically before this change; Consensus, VerifyHybridSecp256k1MLDSA and isXChainNotEnabled are referenced by tests and defined nowhere.
166 lines
4.5 KiB
Go
166 lines
4.5 KiB
Go
// Copyright (C) 2025, Lux Industries Inc All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
// Package warp provides warp message signing and aggregation utilities.
|
|
package warp
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"time"
|
|
|
|
luxlog "github.com/luxfi/log"
|
|
"github.com/luxfi/warp"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
const (
|
|
SignatureAggregatorRequestTimeout = 30 * time.Second
|
|
DefaultQuorumPercentage = 67
|
|
MaxRetries = 3
|
|
InitialBackoff = 1 * time.Second
|
|
)
|
|
|
|
// AggregateSignatureRequest defines the request structure for signature aggregation
|
|
type AggregateSignatureRequest struct {
|
|
Message string
|
|
SigningChainID string
|
|
QuorumPercentage uint64
|
|
Justification string
|
|
}
|
|
|
|
// AggregateSignatureResponse defines the response structure for signature aggregation
|
|
type AggregateSignatureResponse struct {
|
|
SignedMessage string
|
|
}
|
|
|
|
// SignMessage sends a request to the signature aggregator to sign a message.
|
|
// It returns the signed warp message or an error if the operation fails.
|
|
func SignMessage(logger luxlog.Logger, signatureAggregatorEndpoint string, message, justification, signingChainID string, quorumPercentage uint64) (*warp.Envelope, error) {
|
|
if quorumPercentage == 0 {
|
|
quorumPercentage = DefaultQuorumPercentage
|
|
} else if quorumPercentage > 100 {
|
|
return nil, fmt.Errorf("quorum percentage cannot be greater than 100")
|
|
}
|
|
request := AggregateSignatureRequest{
|
|
Message: message,
|
|
SigningChainID: signingChainID,
|
|
QuorumPercentage: quorumPercentage,
|
|
Justification: justification,
|
|
}
|
|
|
|
requestBody, err := json.Marshal(request)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to marshal request: %w", err)
|
|
}
|
|
|
|
logger.Info("Calling signature aggregator",
|
|
zap.String("request", string(requestBody)),
|
|
)
|
|
|
|
client := &http.Client{
|
|
Timeout: SignatureAggregatorRequestTimeout,
|
|
}
|
|
|
|
var lastErr error
|
|
backoff := InitialBackoff
|
|
|
|
for attempt := 0; attempt < MaxRetries; attempt++ {
|
|
if attempt > 0 {
|
|
logger.Info("Retrying signature aggregator request",
|
|
zap.Int("attempt", attempt+1),
|
|
zap.Duration("backoff", backoff),
|
|
)
|
|
time.Sleep(backoff)
|
|
backoff *= 2 // Exponential backoff
|
|
}
|
|
|
|
resp, err := client.Post(
|
|
signatureAggregatorEndpoint,
|
|
"application/json",
|
|
bytes.NewBuffer(requestBody),
|
|
)
|
|
if err != nil {
|
|
lastErr = fmt.Errorf("failed to make request: %w", err)
|
|
logger.Error("Error making request to signature aggregator",
|
|
zap.Error(err),
|
|
zap.Int("attempt", attempt+1),
|
|
)
|
|
continue
|
|
}
|
|
|
|
// Read response body
|
|
body, err := io.ReadAll(resp.Body)
|
|
if closeErr := resp.Body.Close(); closeErr != nil {
|
|
logger.Warn("Failed to close response body",
|
|
zap.Error(closeErr),
|
|
)
|
|
}
|
|
if err != nil {
|
|
lastErr = fmt.Errorf("failed to read response body: %w", err)
|
|
logger.Error("Error reading response body",
|
|
zap.Error(err),
|
|
zap.Int("attempt", attempt+1),
|
|
)
|
|
continue
|
|
}
|
|
|
|
logger.Info("Received response from signature aggregator",
|
|
zap.Int("status_code", resp.StatusCode),
|
|
zap.String("response", string(body)),
|
|
zap.Int("attempt", attempt+1),
|
|
)
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
lastErr = fmt.Errorf("signature aggregator returned non-200 status code: %d, body: %s", resp.StatusCode, string(body))
|
|
logger.Error("Received non-200 status code",
|
|
zap.Int("status_code", resp.StatusCode),
|
|
zap.String("body", string(body)),
|
|
zap.Int("attempt", attempt+1),
|
|
)
|
|
continue
|
|
}
|
|
|
|
var response AggregateSignatureResponse
|
|
if err := json.Unmarshal(body, &response); err != nil {
|
|
lastErr = fmt.Errorf("failed to parse response: %w", err)
|
|
logger.Error("Error parsing response",
|
|
zap.Error(err),
|
|
zap.Int("attempt", attempt+1),
|
|
)
|
|
continue
|
|
}
|
|
|
|
// Decode the hex string
|
|
signedMessageBytes, err := hex.DecodeString(response.SignedMessage)
|
|
if err != nil {
|
|
lastErr = fmt.Errorf("error decoding hex: %w", err)
|
|
logger.Error("Error decoding hex",
|
|
zap.Error(err),
|
|
zap.Int("attempt", attempt+1),
|
|
)
|
|
continue
|
|
}
|
|
|
|
// What the aggregator returns is a message plus the signatures
|
|
// gathered over it — an Envelope. A Message carries no signature.
|
|
signedMessage, err := warp.ParseEnvelope(signedMessageBytes)
|
|
if err != nil {
|
|
lastErr = fmt.Errorf("error parsing signed message: %w", err)
|
|
logger.Error("Error parsing signed message",
|
|
zap.Error(err),
|
|
zap.Int("attempt", attempt+1),
|
|
)
|
|
continue
|
|
}
|
|
|
|
return signedMessage, nil
|
|
}
|
|
|
|
return nil, fmt.Errorf("failed after %d attempts, last error: %w", MaxRetries, lastErr)
|
|
}
|