feat: implement identity key change detection for Signal protocol

This implements the functionality from WhiskeySockets/Baileys PR #2307
with enhanced improvements:

## Core Features
- Extract identity key from PreKeyWhisperMessage before decryption
- Detect when a contact reinstalls WhatsApp (identity key changes)
- Automatically delete old session and recreate on identity change
- Trust On First Use (TOFU) for new contacts

## Improvements over original PR
- LRU cache for identity keys (1000 keys, 30min TTL)
- Integration with existing Circuit Breaker (reset on identity change)
- New Prometheus metrics for observability:
  - signal_identity_changes_total (new/changed)
  - signal_mac_errors_total
  - signal_session_recreations_total
  - signal_identity_key_cache_hits/misses
  - signal_identity_key_operations_ms (histogram)
- New 'identity.changed' event for applications to notify users
- RetryReason enum with MAC_ERROR_CODES and SESSION_ERROR_CODES
- Robust protobuf parsing with validation
- Structured logging with fingerprints

## Technical Details
- Manual protobuf parsing (compatible with any Signal implementation)
- SHA-256 fingerprints for key identification
- Atomic session deletion + identity key save
- Best-effort identity tracking (doesn't fail decryption on errors)

Resolves permanent MAC errors when contacts reinstall WhatsApp.

https://claude.ai/code/session_01SWAcNuGZQmEKyBPYkBhVHg
This commit is contained in:
Claude
2026-01-30 03:35:18 +00:00
parent 228ea998bd
commit 164c7fbe93
5 changed files with 654 additions and 15 deletions
+29
View File
@@ -1601,6 +1601,35 @@ export const metrics = {
new Gauge('prekey_count', 'Current prekey count')
),
// ========== Signal Identity Metrics ==========
/** Total identity key changes detected (contact reinstalled WhatsApp) */
signalIdentityChanges: baileysMetrics.register(
new Counter('signal_identity_changes_total', 'Total identity key changes detected', ['type'])
),
/** Total Signal MAC errors encountered */
signalMacErrors: baileysMetrics.register(
new Counter('signal_mac_errors_total', 'Total Signal MAC verification errors', ['action'])
),
/** Total Signal session recreations by reason */
signalSessionRecreations: baileysMetrics.register(
new Counter('signal_session_recreations_total', 'Total Signal session recreations', ['reason'])
),
/** Identity key cache statistics */
signalIdentityKeyCacheHits: baileysMetrics.register(
new Counter('signal_identity_key_cache_hits_total', 'Total identity key cache hits')
),
signalIdentityKeyCacheMisses: baileysMetrics.register(
new Counter('signal_identity_key_cache_misses_total', 'Total identity key cache misses')
),
/** Identity key operations latency */
signalIdentityKeyOperations: baileysMetrics.register(
new Histogram('signal_identity_key_operations_ms', 'Identity key operation latency in ms', ['operation'], [1, 5, 10, 25, 50, 100])
),
/** Current identity key cache size */
signalIdentityKeyCacheSize: baileysMetrics.register(
new Gauge('signal_identity_key_cache_size', 'Current identity key cache size')
),
// ========== Cache Metrics ==========
cacheHits: baileysMetrics.register(
new Counter('cache_hits_total', 'Total cache hits', ['cache'])