Files
minecraft_protocol/versions/1.19.md
T
claude-timemachine a3d5f64ef5 verify pass: resolve VERIFY flags (corrections + citations + honest UNCONFIRMED)
Corrected real errors: several 1.7.x release dates, resource_pack_send version,
config packet ordering, structured-component count (56), PLAYER_LOADED (1.21.4),
entity_sound_effect field order. Confirmed+cited the rest; remaining ~19 items
re-marked UNCONFIRMED (third-party/ViaLegacy/26.2 internals unreachable from refs).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 15:03:44 +02:00

29 KiB
Raw Blame History

Java Edition 1.19.x — Protocol Deep-Dive

Release Protocol # Release date minecraft-data dir ViaVersion package (into this version)
1.19 759 2022-06-07 data/pc/1.19 v1_18_2to1_19
1.19.1 760 2022-07-27 (no separate dir; = 1.19.2) v1_19to1_19_1
1.19.2 760 2022-08-05 data/pc/1.19.2 v1_19to1_19_1
1.19.3 761 2022-12-07 data/pc/1.19.3 v1_19_1to1_19_3
1.19.4 762 2023-03-14 data/pc/1.19.4 v1_19_3to1_19_4

Protocol numbers cross-checked against minecraft-data protocolVersions.json (1.19→759 dataVersion 3105, 1.19.1→760 dv 3117, 1.19.2→760 dv 3120, 1.19.3→761 dv 3218, 1.19.4→762 dv 3337) and each dir's version.json. 1.19.1 and 1.19.2 share protocol 760 — the wire format is identical; 1.19.2 was a same-week bug-fix that did not bump the protocol. Release dates from minecraft.wiki release articles (fetched 2026-06-19): 1.19, 1.19.1, 1.19.3, 1.19.4. ViaVersion source at /tmp/mcproto-refs/ViaVersion/.


Headline — The Wild Update + Secure Chat

1.19 ("The Wild Update", 2022-06-07) shipped the Deep Dark biome + Warden, Ancient Cities, the Mangrove Swamp, and the Allay/Frog mobs (minecraft.wiki/w/Java_Edition_1.19, fetched 2026-06-19). But on the wire, the dominant story of the whole 1.19 line is chat signing / Secure Chat — not gameplay. The wiki states plainly that as of 1.19, "Chat messages between players, as well as chat from the /say, /msg, /teammsg, and /me commands, are now cryptographically signed", and players receive a Mojang-provided key pair on startup (1.19 article, fetched 2026-06-19).

The reason this release line carries four protocol numbers (759 → 760 → 761 → 762) is that the signing design churned three times:

  • 759 (1.19): v1 — client signs each chat message with a per-message signature, key delivered in Login Start; signed/unsigned content split; chat preview.
  • 760 (1.19.1/1.19.2): v2 — the player-reporting rework. A signature chain is introduced (each message signs over the preceding signature + a set of last-seen message acknowledgements), so message order is cryptographically verifiable for reports.
  • 761 (1.19.3): partial revert + re-architecture. Chat preview removed entirely; per-message "header" packets gone; the chain is rebuilt around a per-session ID + monotonic message index (MessageLink); system messages are no longer in the signed path; signing becomes more conditional on enforce-secure-profile. The disguised-chat packet is introduced for server-authored messages that need chat-type decoration without a signature.
  • 762 (1.19.4): refinements only — no change to the signing payload; clients reset their secure-chat session state on the Login (Play) packet.

This is the most complicated signing story in the protocol's history; the per-patch sub-sections below document each model precisely from ViaVersion source.


Login-phase: the session profile public key (759760 only)

Secure Chat needs the client to prove ownership of a Mojang-signed key pair. In 1.191.19.2 that key is delivered during the Login state, appended to Login Start (Serverbound HELLO), and it is what later chat-message signatures are verified against. This is the protocol's first use of a player-supplied public key in login. Full field layout and the Encryption-Response salt/signature variant are in ../05-login-encryption.md §2 and §5; summarised here for the signing context:

  • Login Start gains an optional Signature (profile key) container (1.191.19.2): Timestamp (i64 key-expiry), Public Key (DER SubjectPublicKeyInfo), and a Mojang Signature over them. ViaVersion's type is Types.OPTIONAL_PROFILE_KEY. In 1.19.1+ Login Start also carries an optional Player UUID.
    • v1_18_2to1_19/Protocol1_18_2To1_19.java:278-284 — serverbound HELLO maps the name and reads-and-discards OPTIONAL_PROFILE_KEY (ViaVersion's own clients downstream don't sign).
    • v1_19to1_19_1/Protocol1_19To1_19_1.java:231-248 — serverbound HELLO reads the incoming profile key but substitutes ViaVersion's own ChatSession1_19_0.getProfileKey() when a chat session is configured, then reads the optional UUID (Types.OPTIONAL_UUID).
  • Encryption Response salt/signature variant (1.191.19.2): when the client holds a profile key it replies to Encryption Request with salt + signature instead of the encrypted verify-token nonce; the server verifies it against the profile key. ViaVersion swaps this back to a plain nonce when its downstream client has no key:
    • v1_18_2to1_19/Protocol1_18_2To1_19.java:286-307 (the salt branch, // 🧂).
    • v1_19to1_19_1/Protocol1_19To1_19_1.java:264-283.
  • 761 (1.19.3) moves the key out of Login Start. The profile key is no longer sent at login; instead the active key is sent in-Play via the new CHAT_SESSION_UPDATE packet (see §761). At login 1.19.3 only carries name + optional UUID:
    • v1_19_1to1_19_3/Protocol1_19_1To1_19_3.java:284-294 — serverbound HELLO writes OPTIONAL_PROFILE_KEY from the chat session (or null) but the client no longer supplies one; :295-322 handles the Encryption-Response salt/signature using the in-Play ChatSession1_19_1 key.

Cross-reference: ../05-login-encryption.md lines 3553 (Login Start Signature container, present 1.191.19.2, removed 1.19.3) and 100123 (Encryption Response profile-key form).


759 — 1.19 (2022-06-07): chat signing v1

Model. Each player chat message is individually signed by the client. There is no chain yet: the signature covers a fixed-width metadata block plus the (canonicalised) decorated message JSON. The server delivers player chat to recipients in a dedicated, signed PLAYER_CHAT packet distinct from SYSTEM_CHAT.

Signature payload (1.19, ChatSession1_19_0). v1_18_2to1_19 is a down-translation (1.18.2-side ↔ 1.19-side), and ViaVersion's own clients don't sign, so the canonical signing payload lives in ChatSession1_19_0.signChatMessage:

// api/.../signature/storage/ChatSession1_19_0.java:42-51
byte[] data = new byte[32];                          // big-endian
buffer.putLong(metadata.salt());                     //  8B  salt
buffer.putLong(sender.MSB).putLong(sender.LSB);      // 16B  sender UUID
buffer.putLong(metadata.timestamp().getEpochSecond);  // 8B  timestamp (seconds)
signer.accept(data);
signer.accept(GsonUtil.sort(content.decorated())     // canonical-sorted JSON of
              .toString().getBytes(UTF_8));            // the decorated component

So the v1 signed bytes are salt(8) ‖ senderUUID(16) ‖ timestampSeconds(8) ‖ sortedDecoratedJSON, signed SHA256withRSA with the player's private key.

Serverbound chat packets (1.19, ServerboundPackets1_19):

Packet ID Fields (1.19)
CHAT_COMMAND 0x03 command String, timestamp i64, salt i64, VarInt array of {argumentName, signature[]}, signedPreview bool
CHAT 0x04 message String, timestamp i64, salt i64, signature byte[], signedPreview bool
CHAT_PREVIEW 0x05 preview request (queryId + text)

Source: v1_18_2to1_19/packet/ServerboundPackets1_19.java:27-29. ViaVersion (translating up from a 1.18 server that has no signing) simply reads and drops all the signing fields and cancels CHAT_PREVIEW: Protocol1_18_2To1_19.java:225-254.

Clientbound chat packets (1.19, ClientboundPackets1_19):

Packet ID Notes
CHAT_PREVIEW 0x0C server-side preview response
PLAYER_CHAT 0x30 signed player message: signedContent (Component), unsignedContent (opt Component), chat-type VarInt, sender UUID, sender name, team name, timestamp i64, salt i64, signature byte[]
SERVER_DATA 0x3F MOTD/icon/previewsChat
SET_DISPLAY_CHAT_PREVIEW 0x4B toggles preview UI
SYSTEM_CHAT 0x5F unsigned system/overlay message (Component + type VarInt)

Source: v1_18_2to1_19/packet/ClientboundPackets1_19.java:36,72,87,99,119. Note the signed/unsigned content split in PLAYER_CHAT — a 1.19 novelty: the server can show an unsigned, server-decorated rendering while still carrying the original signed text for reporting.

ViaVersion from a 1.18 server: there is no signed player chat to translate up, so 1.18's CHAT (0x0F) is rewritten into SYSTEM_CHAT (0x5F) — every incoming message becomes a system message ("we don't want to analyze and remove player names"), which sidesteps signing entirely: Protocol1_18_2To1_19.java:212-223.

ViaVersion package/commits. Package common/.../protocols/v1_18_2to1_19/ (Protocol1_18_2To1_19.java + packet/{Clientbound,Serverbound}Packets1_19.java + provider/AckSequenceProvider.java + storage/{NonceStorage1_19,SequenceStorage,DimensionRegistryStorage}.java). git log --oneline -- common/.../v1_18_2to1_19 (top relevant): ab3927dff "Implement our own hash writing", 32e51b52a "Cleanup LOGIN/STATUS packet handlers (#4113)", c5756fe45 "Rename Position to BlockPosition", 501f65e21 "Packet and entity type renames", e965e9713 "Package/class renames and moves". The original 759 chat-signing implementation commit is not in the log for this path — confirmed via git log --diff-filter=A: the file was first introduced at its current path by e965e9713 (rename). The true origin commit is a12dfa405 "1.19 Experimental Snapshot 1" (found via git log --follow), which predates the package rename and does not appear in a plain git log of the current path.


760 — 1.19.1 / 1.19.2 (2022-07-27 / 2022-08-05): player reporting + signature chain v2

Headline. 1.19.1 added the player-reporting system: players can report abusive chat, multiple messages per report, with categories (harassment, hate speech, etc.); reported players can be banned from online play/Realms after review. Messages that are unsigned or server-tampered are now flagged "Not Secure"/"Modified". enforce-secure-profile now defaults to true on dedicated servers. Crucially: "The order of chat messages are now cryptographically verified" — the chain. (minecraft.wiki/w/Java_Edition_1.19.1, fetched 2026-06-19.) 1.19.2 is wire-identical (still protocol 760) — a bug-fix that did not change the format.

Model — the v2 signature chain (ChatSession1_19_1). Each message now signs over the preceding message's signature (the "header") plus the body (which itself folds in last-seen message acknowledgements). This is what makes message order tamper-evident for reporting.

// api/.../signature/storage/ChatSession1_19_1.java:43-52
MessageHeader header = new MessageHeader(this.precedingSignature, sender);
MessageBody   body   = new MessageBody(content, timestamp, salt, lastSeenMessages);
header.update(signer);                  // preceding-sig ‖ senderUUID
body.update(signer);                    // SHA-256 hash of body, see below
this.precedingSignature = signature;    // chain advances

Header bytes (chain/v1_19_1/MessageHeader.java:38-44): precedingSignature (if present) ‖ senderUUID.

Body — the body is SHA-256-hashed first, then the digest is fed to the signer (chain/v1_19_1/MessageBody.java:53-75). The pre-hash buffer is:

salt(i64) ‖ timestampSeconds(i64) ‖ plainContent(UTF-8) ‖ 0x46
   ‖ [if decorated] sortedDecoratedJSON(UTF-8)
   ‖ for each lastSeenMessage: 0x46 ‖ uuidMSB(i64) ‖ uuidLSB(i64) ‖ signatureBytes

0x46 (= byte 70, HASH_SEPARATOR_BYTE) delimits sections. So v2 differs from v1 in three ways: (a) a SHA-256 pre-hash of the body, (b) inclusion of the plain content plus a separator before the optional decorated JSON, and (c) the last-seen acknowledgement set is part of the signed body.

Serverbound chat (1.19.1, ServerboundPackets1_19_1):

Packet ID Change vs 1.19
CHAT_ACK 0x03 new — acknowledge last-seen messages out of band
CHAT_COMMAND 0x04 +lastSeenMessages array + optional lastReceivedMessage appended
CHAT 0x05 +lastSeenMessages array + optional lastReceivedMessage
CHAT_PREVIEW 0x06 unchanged role

Source: v1_19to1_19_1/packet/ServerboundPackets1_19_1.java:27-30. The IDs all shift by one to make room for CHAT_ACK at 0x03. ViaVersion's signing handler on the serverbound CHAT/CHAT_COMMAND calls chatSession.signChatMessage(metadata, decoratableMessage) and writes the resulting signature + signedPreview = decoratableMessage.isDecorated(), then reads (and drops, for the upstream 1.19-side) the PLAYER_MESSAGE_SIGNATURE_ARRAY last-seen and OPTIONAL_PLAYER_MESSAGE_SIGNATURE: Protocol1_19To1_19_1.java:112-193. CHAT_ACK is cancelled outbound: :194.

Clientbound chat (1.19.1, ClientboundPackets1_19_1):

Packet ID Change vs 1.19
CUSTOM_CHAT_COMPLETIONS 0x15 new
DELETE_CHAT 0x18 new — server retracts a message by signature
PLAYER_CHAT_HEADER 0x32 new — standalone signed header (preceding-sig + body hash) for messages whose body the client already has
PLAYER_CHAT 0x33 reworked: carries the last-seen context + filter mask
SERVER_DATA 0x42 +enforcesSecureChat bool appended
SET_DISPLAY_CHAT_PREVIEW 0x4E unchanged role
SYSTEM_CHAT 0x62 type field becomes an overlay bool downstream

Source: v1_19to1_19_1/packet/ClientboundPackets1_19_1.java:45,48,74,75,90,122. ViaVersion, lacking a way to faithfully reproduce the 760 signed chat onto a 759 client, collapses PLAYER_CHAT (760) back into SYSTEM_CHAT (759) — re-decorating the message via the chat-type registry rather than forwarding signatures: Protocol1_19To1_19_1.java:85-111 + the decorateChatMessage/translatabaleComponentFromTag helpers (:321-417). It also injects the enforcesSecureChat flag into SERVER_DATA from its own config: :221-229 (create(Types.BOOLEAN, Via.getConfig().enforceSecureChat())).

Velocity-forwarding wrinkle. Because the profile key now travels in Velocity modern forwarding, a 760 key would reach a server expecting a 759 key; ViaVersion rewrites the velocity:player_info forwarding-version byte down to 1 in the login CUSTOM_QUERY: Protocol1_19To1_19_1.java:284-308.

ViaVersion package/commits. Package common/.../protocols/v1_19to1_19_1/ (Protocol1_19To1_19_1.java + data/{ChatDecorationResult,ChatRegistry1_19_1}.java + storage/{ChatTypeStorage,NonceStorage1_19_1}.java). git log --oneline -- …/v1_19to1_19_1: fe9ca4992 "Update mcstructs", 29f299d88 "Update MCStructs to 3.0.0 (#4422)", 32e51b52a "Cleanup LOGIN/STATUS packet handlers (#4113)", b1f64fd08 "Use enhanced switches in more places (#4043)". The original 760 chain/reporting implementation commit is not in the log for this path — confirmed via git log --diff-filter=A: the file was created at its current path by e965e9713 (rename from protocol1_19_1to1_19/). The true origin commit is e3e85db02 "1.19.1-pre1" (found via git log --follow on the old path), which predates the package rename.


761 — 1.19.3 (2022-12-07): partial revert + chain v3

Headline. 1.19.3 removed chat preview entirely ("Removed chat preview"); deleted messages now show a "deleted by the server" placeholder for ≥3s; the "Modified" tag stops appearing for style-only server edits. (minecraft.wiki/w/Java_Edition_1.19.3, fetched 2026-06-19.) The wiki release article does not narrate the wire-level chat-security rework — that detail comes from ViaVersion source below.

What was reverted / re-architected at the wire level:

  1. Chat preview gone. All preview packets are dropped: ViaVersion cancels CHAT_PREVIEW, SET_DISPLAY_CHAT_PREVIEW, PLAYER_CHAT_HEADER, and DELETE_CHAT when translating a 1.19.1 server down: v1_19_1to1_19_3/Protocol1_19_1To1_19_3.java:325-328. The standalone PLAYER_CHAT_HEADER packet (0x32 in 760) is gone from the 761 clientbound set.
  2. signedPreview bool removed from serverbound CHAT/CHAT_COMMAND; the message format is acknowledgement-based instead.
  3. Last-seen moves to an offset + bitset (ACKNOWLEDGED_BIT_SET) rather than a full signature array on every message — far more compact: serverbound CHAT (0x05) ends with offset VarInt + ACKNOWLEDGED_BIT_SET: Protocol1_19_1To1_19_3.java:230-270; CHAT_COMMAND (0x04) likewise :176-229.
  4. New CHAT_SESSION_UPDATE (serverbound 0x20) — the player's profile key + a per-session ID are announced in-Play via this packet, no longer in Login Start. ViaVersion cancels it (its downstream clients have no key): Protocol1_19_1To1_19_3.java:324.
  5. DISGUISED_CHAT (clientbound 0x18) introduced for server-authored messages that need chat-type decoration but no signature (e.g. /say, command output) — separating "decorated but unsigned" from genuinely signed PLAYER_CHAT. ViaVersion rewrites a 1.19.1 PLAYER_CHAT down into DISGUISED_CHAT: Protocol1_19_1To1_19_3.java:128-174.
  6. System messages out of the signed path. Combined with DISGUISED_CHAT, system//say-style output no longer rides the signature chain; only genuine player chat is signed, and signing is optional unless enforce-secure-profile forces it.

Model — the v3 chain (ChatSession1_19_3). The chain is rebuilt around a per-session random sessionId + a monotonic message index (MessageLink), replacing the v2 "preceding-signature header":

// api/.../signature/storage/ChatSession1_19_3.java:37-54
private final UUID sessionId = UUID.randomUUID();
private MessageLink link = new MessageLink(uuid, sessionId);   // index 0
...
signer.accept(Ints.toByteArray(1));   // a constant version/prefix int = 1
messageLink.update(signer);           // senderUUID ‖ sessionId ‖ index(i32)
messageBody.update(signer);           // see below

MessageLink.update (chain/v1_19_3/MessageLink.java:45-49): senderUUID ‖ sessionId(UUID) ‖ index(i32), with index incrementing per message (next(), capped at Integer.MAX_VALUE).

MessageBody.update (chain/v1_19_3/MessageBody.java:46-57) is now fed to the signer raw (no intermediate SHA-256 of the whole body):

salt(i64) ‖ timestampSeconds(i64) ‖ contentLength(i32) ‖ content(UTF-8)
   ‖ lastSeenCount(i32) ‖ for each lastSeen: signatureBytes

Differences from v2: (a) the MessageHeader{precedingSig, sender} is replaced by MessageLink{sender, sessionId, index} — order is now proven by a session-scoped counter rather than a literal back-link to the previous signature; (b) a constant int 1 is prefixed; (c) the body uses a length-prefixed plain content and last-seen list (only the signatureBytes, not the UUID, per acknowledged message) and drops the decorated JSON from the signed payload entirely; (d) the body is no longer pre-hashed by the chain code itself.

Serverbound chat (1.19.3, ServerboundPackets1_19_3):

Packet ID Change vs 1.19.1
CHAT_ACK 0x03 now carries a single count (VarInt), not the full signature array of 760. Confirmed: minecraft-data/data/pc/1.19.3/protocol.json packet_message_acknowledgement = [container, [{name: "count", type: "varint"}]].
CHAT_COMMAND 0x04 argument sigs use SIGNATURE_BYTES; ends with offset + ACKNOWLEDGED_BIT_SET
CHAT 0x05 optional SIGNATURE_BYTES, ends with offset + ACKNOWLEDGED_BIT_SET
CHAT_SESSION_UPDATE 0x20 new — profile key + session id

Source: v1_19_1to1_19_3/packet/ServerboundPackets1_19_3.java:27,28,29,56. ViaVersion's serverbound CHAT handler signs with chatSession.signChatMessage(metadata, decoratableMessage, messagesStorage.lastSignatures()) when a session exists, else writes empty signature: Protocol1_19_1To1_19_3.java:230-270.

Clientbound chat (1.19.3, ClientboundPackets1_19_3):

Packet ID Change vs 1.19.1
CUSTOM_CHAT_COMPLETIONS 0x14 renumbered
DELETE_CHAT 0x16 retained (signature-based delete), preview-deletes gone
DISGUISED_CHAT 0x18 new — decorated-but-unsigned
PLAYER_CHAT 0x31 reworked: previousSignature opt + PLAYER_MESSAGE_SIGNATURE + last-seen + filter mask
SERVER_DATA 0x41 MOTD becomes mandatory Component; icon becomes byte[] (1.19.4 change)
SYSTEM_CHAT 0x60 renumbered; system messages now strictly unsigned

Source: v1_19_1to1_19_3/packet/ClientboundPackets1_19_3.java:44,46,48,73,89,120. PLAYER_CHAT_HEADER and the two preview packets are absent from this enum.

Acknowledgement bookkeeping. ViaVersion maintains a ReceivedMessagesStorage: every incoming signed PLAYER_CHAT is recorded, and after 64 unacknowledged it auto-sends a CHAT_ACK: Protocol1_19_1To1_19_3.java:135-148.

ViaVersion package/commits. Package common/.../protocols/v1_19_1to1_19_3/ (Protocol1_19_1To1_19_3.java + storage/{NonceStorage1_19_3,ReceivedMessagesStorage}.java + rewriters). git log --oneline -- …/v1_19_1to1_19_3 (relevant): 3eec520eb "Send enable features packet after the play login packet in 1.19.1->1.19.3 (#4205)", 3caaed00d "Write enabled features as string array", 815ec24af "Remove removed registries from command arguments", ab3927dff "Implement our own hash writing", 32e51b52a "Cleanup LOGIN/STATUS packet handlers (#4113)". The original 761 chat-rework implementation commit is not in the log for this path — confirmed via git log --diff-filter=A: the file was created at its current path by e965e9713 (rename from protocol1_19_3to1_19_1/). The true origin commit is 14b11bdd1 "Start working on 22w42a" (found via git log --follow on the old path), which predates the package rename.


762 — 1.19.4 (2023-03-14): refinements (no signing-payload change)

Headline. 1.19.4 added Display entities, Interaction entities, armour trims, archaeology (brush + suspicious sand), the Cherry Grove biome, the Sniffer, and /ride + /damage. (minecraft.wiki/w/Java_Edition_1.19.4, fetched 2026-06-19.)

Chat / signing: the signed-message payload did not change from 761. The only chat-security change is procedural: "Clients now reset their secure chat session state when receiving the login packet" (1.19.4 article, fetched 2026-06-19) — i.e. the MessageLink index/session resets on a (re)Login, so a dimension change / server-switch starts a fresh chain. ViaVersion's v1_19_3to1_19_4 package contains no chat-signing handler at all; its registerPackets only touches COMMANDS, UPDATE_MOB_EFFECT (infinite-duration 1 sentinel), and SERVER_DATA (MOTD → mandatory Component, icon String → OPTIONAL_BYTE_ARRAY_PRIMITIVE): v1_19_3to1_19_4/Protocol1_19_3To1_19_4.java:60-105. The CHAT/PLAYER_CHAT/DISGUISED_CHAT packets pass through structurally unchanged at 762 (no rewriter registered for them).

ViaVersion package/commits. Package common/.../protocols/v1_19_3to1_19_4/. git log --oneline -- …/v1_19_3to1_19_4: b4d8fe6ba "Display infinite potion effect duration for 1.19.4+…(#4953)", 97aff00ce "Fill registries used by delayed item loading", c13b40a37 "Add ParticleRewriter base (#4203)", 501f65e21 "Packet and entity type renames". No chat-signing commits — consistent with "no payload change at 762".


Signature-chain evolution (759 → 761)

flowchart TD
    subgraph v1["759 (1.19) — ChatSession1_19_0"]
        A1["per-message signature<br>NO chain"]
        A2["payload: salt ‖ senderUUID ‖ timestamp ‖ sortedDecoratedJSON"]
        A1 --> A2
    end
    subgraph v2["760 (1.19.1/1.19.2) — ChatSession1_19_1"]
        B1["chain via PRECEDING signature<br>(MessageHeader)"]
        B2["header: precedingSig ‖ senderUUID"]
        B3["body (SHA-256 hashed): salt ‖ ts ‖ plain ‖ 0x46 [‖ decoratedJSON]<br>‖ for each lastSeen: 0x46 ‖ uuid ‖ sig"]
        B1 --> B2 --> B3
    end
    subgraph v3["761 (1.19.3) — ChatSession1_19_3"]
        C1["chain via sessionId + index<br>(MessageLink)"]
        C2["prefix int 1 ‖ link: senderUUID ‖ sessionId ‖ index"]
        C3["body: salt ‖ ts ‖ len ‖ content ‖ lastSeenCount ‖ sigs<br>(no decoratedJSON, no decorated path)"]
        C1 --> C2 --> C3
    end
    v1 -->|"+ player reporting,<br>last-seen acks"| v2
    v2 -->|"preview removed,<br>header→sessionId+index,<br>DISGUISED_CHAT split"| v3

Sources: ChatSession1_19_0.java:42-51, ChatSession1_19_1.java:43-52 + chain/v1_19_1/{MessageHeader,MessageBody}.java, ChatSession1_19_3.java:37-54 + chain/v1_19_3/{MessageLink,MessageBody}.java.


Proxy / translation impact

A proxy or version-translation layer sitting across the 1.19 line must understand four mutually-incompatible chat shapes, because each protocol bump moved the signature fields and the chain rules:

  1. All four signing models coexist in the wild. Vanilla 1.19, 1.19.1/.2, 1.19.3, and 1.19.4 clients each speak a different chat-packet shape. A proxy that translates between them (ViaVersion) cannot just renumber packets — it must re-sign or strip-and-downgrade the message:
    • Down-translating signed chat to an older format usually means discarding the signature and re-rendering as a system/disguised message (ViaVersion does exactly this: 760→759 collapses PLAYER_CHATSYSTEM_CHAT (Protocol1_19To1_19_1.java:85-111); 761→760 turns PLAYER_CHATDISGUISED_CHAT (Protocol1_19_1To1_19_3.java:128-174)).
    • When the proxy itself holds the player's key (a configured ChatSession), it forges valid signatures for the target format (signChatMessage(...) in each protocol's serverbound CHAT/CHAT_COMMAND handler).
  2. Risk: relaying stripped signatures to an enforce-secure-profile server. If enforce-secure-profile=true (default since 1.19.1), a server rejects unsigned or invalidly-signed player chat. A proxy that strips signatures while down/upgrading — without re-signing — will have its players' chat dropped or the connection kicked. ViaVersion mitigates by (a) substituting its own ChatSession key in Login Start (759/760) / CHAT_SESSION_UPDATE (761), and (b) re-signing per target format. A proxy that does not hold the key can only forward to non-enforcing servers, or must route player chat as system/disguised (unsigned) messages — which the target may flag "Not Secure" or reject.
  3. enforcesSecureChat flag must be synthesised. SERVER_DATA gained the enforcesSecureChat bool at 760; a proxy bridging from an older server has to fabricate it (ViaVersion sources it from Via.getConfig().enforceSecureChat(): Protocol1_19To1_19_1.java:227).
  4. Last-seen / acknowledgement bookkeeping is mandatory at 760+. Because the chain folds in last-seen signatures, a translating proxy must track received PLAYER_CHAT signatures and emit CHAT_ACK/offset+bitset acknowledgements, or the server's chain validation desyncs (ViaVersion's ReceivedMessagesStorage, auto-CHAT_ACK at 64 unacked: Protocol1_19_1To1_19_3.java:135-148).
  5. Login-phase profile key handling differs 759/760 vs 761. A proxy must read/strip/substitute the Login-Start profile key and the Encryption-Response salt/signature variant for 759/760, but switch to the in-Play CHAT_SESSION_UPDATE path for 761+. Velocity modern-forwarding carries the key too, so the forwarding-version byte may need clamping (Protocol1_19To1_19_1.java:284-308). See ../05-login-encryption.md §2/§5.
  6. 762 is cheap. No signing change; a proxy only resets per-session chat state on Login and handles the SERVER_DATA MOTD/icon type change.

Sources

  • minecraft.wiki release articles (fetched 2026-06-19): 1.19, 1.19.1, 1.19.3, 1.19.4.
  • ViaVersion /tmp/mcproto-refs/ViaVersion/common/.../protocols/{v1_18_2to1_19, v1_19to1_19_1, v1_19_1to1_19_3, v1_19_3to1_19_4}/ (Protocol classes + packet enums) and api/.../minecraft/signature/ (ChatSession1_19_0/1_19_1/1_19_3 + chain/v1_19_1/{MessageHeader,MessageBody} + chain/v1_19_3/{MessageLink,MessageBody}). Git logs per package as cited.
  • minecraft-data /tmp/mcproto-refs/minecraft-data/data/pc/1.19,1.19.2,1.19.3,1.19.4 version.json + common/protocolVersions.json (protocol numbers + dataVersions).
  • Cross-link: ../05-login-encryption.md (profile keys in Login Start; Encryption Response salt/signature form).