Syncd protocol codec — encode/decode snapshots, patches, and mutations with MAC generation and verification.
Pure functions that implement the Baileys Syncd data transformation pipeline.
All crypto operations use Erlang :crypto. Non-deterministic inputs (random IV)
are injectable via options for testing.
Ports chat-utils.ts:45-489 — generateMac, generateSnapshotMac,
generatePatchMac, makeLtHashGenerator, decodeSyncdMutations,
decodeSyncdPatch, decodeSyncdSnapshot, decodePatches, encodeSyncdPatch,
extractSyncdPatches.
Summary
Functions
Decode a sequence of Syncd patches, maintaining state across them.
Decode a list of Syncd mutations, verifying MACs and decrypting values.
Decode a single Syncd patch, verifying the patch MAC and decoding all mutations.
Decode a full Syncd snapshot, verifying the snapshot MAC and extracting all mutations.
Encode a single outbound Syncd patch.
Extract Syncd patches and snapshots from a server response binary node.
Finalize the LTHash generator — apply accumulated adds/subs to the hash.
Generate a value MAC for a mutation record.
Generate a patch MAC that chains to the previous snapshot.
Generate a snapshot MAC from the LTHash state.
Create an LTHash generator that accumulates mix operations.
Mix a single mutation into the LTHash generator state.
Create a fresh LTHash state with version 0 and a 128-byte zero hash.
Valid Syncd collection names.
Types
@type chat_mutation_map() :: %{required(String.t()) => chat_mutation()}
@type chat_mutation_order() :: [chat_mutation()]
@type lt_hash_state() :: %{ version: non_neg_integer(), hash: binary(), index_value_map: %{required(String.t()) => %{value_mac: binary()}} }
@type mutation_operation() :: :set | :remove
@type patch_name() ::
:critical_block
| :critical_unblock_low
| :regular_high
| :regular_low
| :regular
Functions
@spec decode_patches( patch_name(), [map()], lt_hash_state(), (String.t() -> {:ok, map()} | {:error, term()}), non_neg_integer() | nil, boolean(), keyword() ) :: {:ok, %{ state: lt_hash_state(), mutation_map: chat_mutation_map(), mutation_order: chat_mutation_order() }} | {:error, term()}
Decode a sequence of Syncd patches, maintaining state across them.
Ports decodePatches from chat-utils.ts:422-489.
@spec decode_syncd_mutations( [map()], lt_hash_state(), (String.t() -> {:ok, map()} | {:error, term()}), (chat_mutation() -> any()), boolean() ) :: {:ok, %{hash: binary(), index_value_map: map()}} | {:error, term()}
Decode a list of Syncd mutations, verifying MACs and decrypting values.
For each mutation:
- Extract operation and record
- Fetch decryption key via
get_app_state_sync_keycallback - Verify value MAC (if
validate_macs) - AES-256-CBC decrypt the value
- Decode SyncActionData protobuf
- Verify index MAC (if
validate_macs) - Call
on_mutationcallback - Mix into LTHash generator
Returns the updated LTHash state (hash + index_value_map).
Ports decodeSyncdMutations from chat-utils.ts:197-270.
@spec decode_syncd_patch( map(), patch_name(), lt_hash_state(), (String.t() -> {:ok, map()} | {:error, term()}), (chat_mutation() -> any()), boolean() ) :: {:ok, %{hash: binary(), index_value_map: map()}} | {:error, term()}
Decode a single Syncd patch, verifying the patch MAC and decoding all mutations.
Ports decodeSyncdPatch from chat-utils.ts:272-304.
@spec decode_syncd_snapshot( patch_name(), map(), (String.t() -> {:ok, map()} | {:error, term()}), non_neg_integer() | nil, boolean() ) :: {:ok, %{ state: lt_hash_state(), mutation_map: chat_mutation_map(), mutation_order: chat_mutation_order() }} | {:error, term()}
Decode a full Syncd snapshot, verifying the snapshot MAC and extracting all mutations.
Ports decodeSyncdSnapshot from chat-utils.ts:374-420.
@spec encode_syncd_patch( map(), String.t(), lt_hash_state(), (String.t() -> {:ok, map()} | {:error, term()}), keyword() ) :: {:ok, %{patch: map(), state: lt_hash_state()}} | {:error, term()}
Encode a single outbound Syncd patch.
Steps:
- Fetch encryption key via
get_app_state_sync_key - Encode SyncActionData protobuf
- AES-256-CBC encrypt with random IV (injectable via
opts[:iv]) - Generate value MAC, index MAC
- Update LTHash state
- Generate snapshot MAC, patch MAC
- Build SyncdPatch protobuf
Returns {:ok, %{patch: SyncdPatch.t(), state: lt_hash_state()}}.
Ports encodeSyncdPatch from chat-utils.ts:132-195.
@spec extract_syncd_patches( map(), keyword() ) :: {:ok, %{ required(atom()) => %{ patches: [map()], has_more_patches: boolean(), snapshot: map() | nil } }} | {:error, term()}
Extract Syncd patches and snapshots from a server response binary node.
Ports extractSyncdPatches from chat-utils.ts:306-356.
Finalize the LTHash generator — apply accumulated adds/subs to the hash.
Ports the finish() closure from makeLtHashGenerator in chat-utils.ts:103-111.
@spec generate_mac(mutation_operation(), binary(), binary(), binary()) :: binary()
Generate a value MAC for a mutation record.
HMAC-SHA512 over (op_byte || key_id) || encrypted_value || (8-byte length),
truncated to the first 32 bytes.
Ports generateMac from chat-utils.ts:45-67.
@spec generate_patch_mac( binary(), [binary()], non_neg_integer(), patch_name(), binary() ) :: binary()
Generate a patch MAC that chains to the previous snapshot.
HMAC-SHA256 over snapshot_mac || value_macs... || version_64bit_be || name_utf8.
Ports generatePatchMac from chat-utils.ts:119-128.
@spec generate_snapshot_mac(binary(), non_neg_integer(), patch_name(), binary()) :: binary()
Generate a snapshot MAC from the LTHash state.
HMAC-SHA256 over lt_hash || version_64bit_be || name_utf8.
Ports generateSnapshotMac from chat-utils.ts:114-117.
@spec init_lt_hash_generator(lt_hash_state()) :: %{ index_value_map: map(), add_buffs: [binary()], sub_buffs: [binary()], hash: binary() }
Create an LTHash generator that accumulates mix operations.
Returns {state, mix_fn, finish_fn} where:
mix_fn.(state, %{index_mac:, value_mac:, operation:})→ updated statefinish_fn.(state)→%{hash:, index_value_map:}
This is a functional equivalent of Baileys' closure-based makeLtHashGenerator.
Use with Enum.reduce/3 over mutations.
@spec mix_mutation(map(), %{ index_mac: binary(), value_mac: binary(), operation: mutation_operation() }) :: map()
Mix a single mutation into the LTHash generator state.
Ports the mix() closure from makeLtHashGenerator in chat-utils.ts:83-101.
@spec new_lt_hash_state() :: lt_hash_state()
Create a fresh LTHash state with version 0 and a 128-byte zero hash.
Ports newLTHashState() from chat-utils.ts:130.
@spec patch_names() :: [patch_name()]
Valid Syncd collection names.