Files

406 lines
12 KiB
Go
Raw Permalink Normal View History

package processor
import (
"context"
"crypto/ecdsa"
"encoding/hex"
"github.com/golang/protobuf/proto"
"github.com/pkg/errors"
otelattribute "go.opentelemetry.io/otel/attribute"
oteltrace "go.opentelemetry.io/otel/trace"
"go.uber.org/zap"
"github.com/status-im/status-go/internal/crypto"
"github.com/status-im/status-go/internal/crypto/types"
"github.com/status-im/status-go/internal/instrumentation/trace"
2026-04-16 13:22:54 +01:00
adapters "github.com/status-im/status-go/pkg/messaging/adapters"
common "github.com/status-im/status-go/pkg/messaging/common"
"github.com/status-im/status-go/pkg/messaging/controller/utils"
2026-04-16 13:22:54 +01:00
encryption "github.com/status-im/status-go/pkg/messaging/layers/encryption"
"github.com/status-im/status-go/pkg/messaging/layers/encryption/sharedsecret"
"github.com/status-im/status-go/pkg/messaging/layers/segmentation"
2025-12-22 19:57:36 +00:00
messagingtypes "github.com/status-im/status-go/pkg/messaging/types"
"github.com/status-im/status-go/pkg/pubsub"
)
const (
maxNumOfEphemeralKeys = 3
)
var errReliabilityNotStarted = errors.New("reliability not started")
type Processor struct {
identity *ecdsa.PrivateKey
2026-04-16 13:22:54 +01:00
stack *common.MessagingStack
2026-04-16 13:22:54 +01:00
messageConfirmationStorage common.MessageConfirmationPersistence
hashRatchetStorage common.HashRatchetPersistence
ephemeralKeysManager *EphemeralKeysManager
publisher *pubsub.Publisher
logger *zap.Logger
tracer trace.Tracer
}
func NewProcessor(
identity *ecdsa.PrivateKey,
2026-04-16 13:22:54 +01:00
stack *common.MessagingStack,
messageConfirmationStorage common.MessageConfirmationPersistence,
hashRatchetStorage common.HashRatchetPersistence,
logger *zap.Logger,
tracer trace.Tracer,
) *Processor {
return &Processor{
identity: identity,
stack: stack,
messageConfirmationStorage: messageConfirmationStorage,
hashRatchetStorage: hashRatchetStorage,
ephemeralKeysManager: NewEphemeralKeysManager(maxNumOfEphemeralKeys),
publisher: pubsub.NewPublisher(),
logger: logger.Named("processor"),
tracer: tracer,
}
}
func (r *Processor) Publisher() *pubsub.Publisher {
return r.publisher
}
func (r *Processor) GetEphemeralKey() (*ecdsa.PrivateKey, error) {
key, err := r.ephemeralKeysManager.GetRandom()
if err != nil {
return nil, err
}
_, err = r.stack.Transport.LoadKeyFilters(key)
if err != nil {
return nil, err
}
return key, nil
}
2025-12-22 19:57:36 +00:00
func (r *Processor) ProcessMessage(msg *messagingtypes.ReceivedMessage) (*messagingtypes.HandleMessageResponse, error) {
response, err := r.processMessage(msg)
if response == nil || err != nil {
return nil, err
}
// Process queued hash ratchet messages
for _, message := range response.messages {
queuedMessagesResponse, err := r.processQueuedHashRatchetMessages(message.EncryptionLayer.HashRatchetInfo)
if err != nil {
return nil, err
}
response.messages = append(response.messages, queuedMessagesResponse.messages...)
response.ackedMessageIDs = append(response.ackedMessageIDs, queuedMessagesResponse.ackedMessageIDs...)
}
2025-12-22 19:57:36 +00:00
return &messagingtypes.HandleMessageResponse{
Messages: response.messages,
AckedMessageIDs: response.ackedMessageIDs,
}, nil
}
type processMessageResponse struct {
2025-12-22 19:57:36 +00:00
messages []*messagingtypes.Message
ackedMessageIDs []types.HexBytes
}
2025-12-22 19:57:36 +00:00
func (r *Processor) processMessage(m *messagingtypes.ReceivedMessage) (*processMessageResponse, error) {
logger := r.logger.With(zap.Stringer("hash", types.HexBytes(m.Hash)))
logger.Debug("processing received message")
2025-12-22 19:57:36 +00:00
responseMessage := &messagingtypes.Message{}
response := &processMessageResponse{
2025-12-22 19:57:36 +00:00
messages: []*messagingtypes.Message{responseMessage},
ackedMessageIDs: []types.HexBytes{},
}
err := processTransportLayer(responseMessage, m)
if err != nil {
logger.Error("failed to process transport layer", zap.Error(err))
return nil, err
}
err = r.processSegmentationLayer(responseMessage)
if err != nil {
return nil, err
}
hashes := [][]byte{m.Hash}
if responseMessage.SegmentationLayer.Segmented {
// Segments not completed yet, stop processing
if !responseMessage.SegmentationLayer.Completed {
return nil, nil
}
hashes = responseMessage.SegmentationLayer.Hashes
}
ctx, span := r.tracer.Start(trace.DeriveRemoteContext(utils.MergeByteSlices(hashes)), "Processor.processMessage",
oteltrace.WithAttributes(
otelattribute.String("hash", types.EncodeHex(m.Hash)),
otelattribute.StringSlice("hashes", types.EncodeHexes(hashes)),
),
)
defer span.End()
err = r.processEncryptionLayer(ctx, responseMessage, logger)
if err == nil {
span.AddEvent("encryption layer processed")
} else {
// Hash ratchet with a group id not found yet, save the message for future processing
2026-04-16 13:22:54 +01:00
if err == encryption.ErrHashRatchetGroupIDNotFound && len(responseMessage.EncryptionLayer.HashRatchetInfo) == 1 {
info := responseMessage.EncryptionLayer.HashRatchetInfo[0]
span.AddEvent("hash ratchet with group id not found yet", oteltrace.WithAttributes(
otelattribute.String("groupID", types.ToHex(info.GroupID)),
))
return nil, r.hashRatchetStorage.SaveMessage(info.GroupID, info.KeyID, m)
} else {
span.AddEvent("encryption layer not processed", oteltrace.WithAttributes(
otelattribute.String("error", err.Error()),
))
logger.Debug("failed to process encryption layer", zap.Error(err))
}
}
// A broken SDS layer yields a payload that silently fails to decode further up,
// so fail the whole envelope instead: it must be retried, not marked processed.
2025-12-22 19:57:36 +00:00
err = r.processSDSLayer(responseMessage)
if err != nil {
logger.Error("failed to unwrap payload for SDS", zap.Error(err))
return nil, err
2025-12-22 19:57:36 +00:00
}
messages, ackedMessageIDs, err := r.processReliabilityLayer(responseMessage, logger)
if err == nil {
span.AddEvent("reliability layer processed")
response.messages = messages
response.ackedMessageIDs = ackedMessageIDs
} else {
span.AddEvent("reliability layer not processed")
logger.Debug("failed to process reliability layer", zap.Error(err))
}
return response, nil
}
2025-12-22 19:57:36 +00:00
func (r *Processor) processQueuedHashRatchetMessages(hashRatchetInfos []*messagingtypes.HashRatchetInfo) (*processMessageResponse, error) {
response := &processMessageResponse{
2025-12-22 19:57:36 +00:00
messages: []*messagingtypes.Message{},
ackedMessageIDs: []types.HexBytes{},
}
for _, hashRatchetInfo := range hashRatchetInfos {
messages, err := r.hashRatchetStorage.GetMessages(hashRatchetInfo.KeyID)
if err != nil {
return nil, err
}
var processedIds [][]byte
for _, message := range messages {
logger := r.logger.With(zap.String("hash", types.EncodeHex(message.Hash)))
logger.Debug("processing queued hash ratchet message")
r, err := r.processMessage(message)
if err != nil {
continue
}
if r == nil {
// Not processed: the message is still incomplete (segmentation) or was
// re-queued waiting for its hash ratchet key — leave it in the queue.
continue
}
processedIds = append(processedIds, message.Hash)
response.messages = append(response.messages, r.messages...)
response.ackedMessageIDs = append(response.ackedMessageIDs, r.ackedMessageIDs...)
}
err = r.hashRatchetStorage.DeleteMessages(processedIds)
if err != nil {
r.logger.Warn("failed to delete hash ratchet messages", zap.Error(err))
return nil, err
}
}
return response, nil
}
2025-12-22 19:57:36 +00:00
func processTransportLayer(m *messagingtypes.Message, receivedMessage *messagingtypes.ReceivedMessage) error {
publicKey, err := crypto.UnmarshalPubkey(receivedMessage.Sig)
if err != nil {
return errors.Wrap(err, "failed to get signature")
}
m.TransportLayer.Message = receivedMessage
m.TransportLayer.Hash = receivedMessage.Hash
m.TransportLayer.SigPubKey = publicKey
m.TransportLayer.Payload = receivedMessage.Payload
if receivedMessage.Dst != nil {
publicKey, err := crypto.UnmarshalPubkey(receivedMessage.Dst)
if err != nil {
return err
}
m.TransportLayer.Dst = publicKey
}
return nil
}
2025-12-22 19:57:36 +00:00
func (r *Processor) processSegmentationLayer(m *messagingtypes.Message) error {
reconstructedPayload, transportIDs, err := r.stack.Segmentation.Reconstruct(
m.TransportLayer.Payload,
m.TransportLayer.SigPubKey,
m.TransportLayer.Hash)
switch err {
case nil:
m.TransportLayer.Payload = reconstructedPayload
m.SegmentationLayer.Segmented = true
m.SegmentationLayer.Completed = true
m.SegmentationLayer.Hashes = transportIDs
case segmentation.ErrIncomplete:
m.SegmentationLayer.Segmented = true
m.SegmentationLayer.Completed = false
err = nil
case segmentation.ErrAlreadyCompleted:
// A duplicate segment for an already reconstructed message should be ignored.
m.SegmentationLayer.Segmented = true
m.SegmentationLayer.Completed = false
err = nil
case segmentation.ErrInvalidPayload:
m.SegmentationLayer.Segmented = false
m.SegmentationLayer.Completed = false
err = nil
}
return err
}
2025-12-22 19:57:36 +00:00
func (r *Processor) processEncryptionLayer(ctx context.Context, m *messagingtypes.Message, logger *zap.Logger) error {
logger = logger.Named("processEncryptionLayer")
ctx, span := r.tracer.Start(ctx, "Processor.processEncryptionLayer")
defer span.End()
// As we handle non-encrypted messages, we make sure that DecryptPayload
// is set regardless of whether this step is successful
m.EncryptionLayer.Payload = m.TransportLayer.Payload
// if it's an ephemeral key, we don't negotiate a topic
ephemeralKey := r.ephemeralKeysManager.GetPrivateKeyFor(m.TransportLayer.Dst)
if ephemeralKey != nil {
span.AddEvent("targeted ephemeral key")
return nil
}
2026-04-16 13:22:54 +01:00
var protocolMessage encryption.ProtocolMessage
err := proto.Unmarshal(m.TransportLayer.Payload, &protocolMessage)
if err != nil {
return errors.Wrap(err, "failed to unmarshal ProtocolMessage")
}
response, err := r.stack.Encryption.HandleMessage(
ctx,
r.identity,
m.SigPubKey(),
&protocolMessage,
m.TransportLayer.Hash,
)
switch err {
case nil:
m.EncryptionLayer.Payload = response.DecryptedMessage
2026-04-16 13:22:54 +01:00
m.EncryptionLayer.Installations = adapters.FromEncryptionInstallations(response.Installations)
m.EncryptionLayer.HashRatchetInfo = adapters.FromEncryptionHashRatchets(response.HashRatchetInfo)
err := r.ProcessSharedSecrets(response.SharedSecrets)
if err != nil {
logger.Error("failed to process shared secrets", zap.Error(err))
}
2026-04-16 13:22:54 +01:00
case encryption.ErrHashRatchetGroupIDNotFound:
if response != nil {
2026-04-16 13:22:54 +01:00
m.EncryptionLayer.HashRatchetInfo = adapters.FromEncryptionHashRatchets(response.HashRatchetInfo)
}
2026-04-16 13:22:54 +01:00
case encryption.ErrDeviceNotFound:
pubsub.Publish(r.publisher, SenderUnawareOfInstallation{
PublicKey: m.SigPubKey(),
})
default:
logger.Error("failed to decrypt message", zap.Error(err))
}
return err
}
2025-12-22 19:57:36 +00:00
func (r *Processor) processSDSLayer(msg *messagingtypes.Message) error {
if len(msg.EncryptionLayer.Payload) <= 0 {
return nil
}
unwrappedPayload, err := r.stack.Reliability.UnwrapPayloadFromSDS(msg.EncryptionLayer.Payload)
if err != nil {
return err
}
msg.EncryptionLayer.Payload = unwrappedPayload
return nil
}
func (r *Processor) ProcessSharedSecrets(secrets []*sharedsecret.Secret) error {
for _, secret := range secrets {
2025-12-22 19:57:36 +00:00
_, err := r.stack.Transport.ProcessNegotiatedSecret(messagingtypes.NegotiatedSecret{
PublicKey: secret.Identity,
Key: secret.Key,
})
if err != nil {
return err
}
}
return nil
}
2025-12-22 19:57:36 +00:00
func (r *Processor) processReliabilityLayer(m *messagingtypes.Message, logger *zap.Logger) ([]*messagingtypes.Message, []types.HexBytes, error) {
if !r.stack.Reliability.Started() {
return nil, nil, errReliabilityNotStarted
}
datasyncMessage, err := r.stack.Reliability.UnwrapAndAcknowledgeMessage(
m.SigPubKey(),
m.EncryptionLayer.Payload,
)
if err != nil {
return nil, nil, err
}
2025-12-22 19:57:36 +00:00
var statusMessages []*messagingtypes.Message
for _, ds := range datasyncMessage.Messages {
message, err := m.Clone()
if err != nil {
return nil, nil, err
}
message.EncryptionLayer.Payload = ds.Body
statusMessages = append(statusMessages, message)
}
ackedMessageIDs := make([]types.HexBytes, 0, len(datasyncMessage.Acks))
for _, ack := range datasyncMessage.Acks {
messageID, err := r.messageConfirmationStorage.MarkAsConfirmed(ack, true)
if err != nil {
logger.Info("got datasync acknowledge for message we don't have in db", zap.String("ack", hex.EncodeToString(ack)))
continue
}
r.stack.Transport.ConfirmMessageDelivered(messageID.String())
ackedMessageIDs = append(ackedMessageIDs, messageID)
}
return statusMessages, ackedMessageIDs, nil
}