From 65b5acf335e7f2831dc4ea1597be0341ece71405 Mon Sep 17 00:00:00 2001 From: Thiago Di Giorgio Date: Wed, 12 Aug 2026 22:06:05 +0000 Subject: [PATCH] fix(auth-state): restore AppStateSyncKeyData.fromObject for app-state sync keys The three auth-state providers deserialise the persisted app-state sync key with `proto.Message.AppStateSyncKeyData.create(value)`. `create()` is a bare constructor that copies own properties with no type coercion, so `keyData` stays the base64 **string** it was serialised as, instead of becoming bytes. Serialisation turns it into a string because protobufjs `Message.toJSON()` uses `util.toJSONOptions` (`bytes: String`), which converts the `Uint8Array` to base64 before `BufferJSON.replacer` ever sees the value. Baileys' own reference implementation therefore reads it back with `.fromObject()`, which base64-decodes `bytes` fields (see `Utils/use-multi-file-auth-state.ts`). With a string, `hkdf()` evaluates `new Uint8Array("7ae+...")` -> ToIndex(NaN) -> length 0, so every mutation key is derived from EMPTY key material. The observable result is app-state sync failing permanently: Invalid patch mac failed to sync state from version, removing and trying from scratch error:1C800064:Provider routines::bad decrypt resyncing regular from v0 (looping forever) It is invisible for the first few minutes after pairing, because `makeCacheableSignalKeyStore` serves the original Buffer-bearing object from its NodeCache (`SIGNAL_STORE`, 5 min TTL). Once that entry expires the cold read returns the corrupt string, and the corrupt value is written back into the cache, so it never recovers. Everything carried by the `regular` collection is lost, which is how this surfaces in practice: chat labels applied on the phone never reach the webhook, `labels.association` stops firing entirely after pairing. Introduced in 8830f476e898fc3d86a6678d44369a1b61a1f244 (v2.3.3); 2.3.2 is clean. Present on 2.3.3 through 2.3.7, main and develop. Verified on 2.3.7 with a real instance: after the change the same instance resynced `regular` from v0 to v19 with zero decrypt errors, and label events started flowing again. Keys already persisted in the broken form are recovered as-is, so no re-pairing is required. Co-Authored-By: Claude Opus 5 (1M context) --- src/utils/use-multi-file-auth-state-prisma.ts | 2 +- src/utils/use-multi-file-auth-state-provider-files.ts | 2 +- src/utils/use-multi-file-auth-state-redis-db.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/utils/use-multi-file-auth-state-prisma.ts b/src/utils/use-multi-file-auth-state-prisma.ts index d090780234..bd11afa2fe 100644 --- a/src/utils/use-multi-file-auth-state-prisma.ts +++ b/src/utils/use-multi-file-auth-state-prisma.ts @@ -182,7 +182,7 @@ export default async function useMultiFileAuthStatePrisma( ids.map(async (id) => { let value = await readData(`${type}-${id}`); if (type === 'app-state-sync-key' && value) { - value = proto.Message.AppStateSyncKeyData.create(value); + value = proto.Message.AppStateSyncKeyData.fromObject(value); } data[id] = value; diff --git a/src/utils/use-multi-file-auth-state-provider-files.ts b/src/utils/use-multi-file-auth-state-provider-files.ts index eecc3100e5..157918169d 100644 --- a/src/utils/use-multi-file-auth-state-provider-files.ts +++ b/src/utils/use-multi-file-auth-state-provider-files.ts @@ -116,7 +116,7 @@ export class AuthStateProvider { ids.map(async (id) => { let value = await readData(`${type}-${id}`); if (type === 'app-state-sync-key' && value) { - value = proto.Message.AppStateSyncKeyData.create(value); + value = proto.Message.AppStateSyncKeyData.fromObject(value); } data[id] = value; diff --git a/src/utils/use-multi-file-auth-state-redis-db.ts b/src/utils/use-multi-file-auth-state-redis-db.ts index e0981c700c..4d5d5a5457 100644 --- a/src/utils/use-multi-file-auth-state-redis-db.ts +++ b/src/utils/use-multi-file-auth-state-redis-db.ts @@ -61,7 +61,7 @@ export async function useMultiFileAuthStateRedisDb( ids.map(async (id) => { let value = await readData(`${type}-${id}`); if (type === 'app-state-sync-key' && value) { - value = proto.Message.AppStateSyncKeyData.create(value); + value = proto.Message.AppStateSyncKeyData.fromObject(value); } data[id] = value;