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>
29 KiB
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 onenforce-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 (759–760 only)
Secure Chat needs the client to prove ownership of a Mojang-signed key pair. In 1.19–1.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.19–1.19.2):Timestamp(i64 key-expiry),Public Key(DERSubjectPublicKeyInfo), and a MojangSignatureover them. ViaVersion's type isTypes.OPTIONAL_PROFILE_KEY. In 1.19.1+ Login Start also carries an optionalPlayer UUID.v1_18_2to1_19/Protocol1_18_2To1_19.java:278-284— serverboundHELLOmaps the name and reads-and-discardsOPTIONAL_PROFILE_KEY(ViaVersion's own clients downstream don't sign).v1_19to1_19_1/Protocol1_19To1_19_1.java:231-248— serverboundHELLOreads the incoming profile key but substitutes ViaVersion's ownChatSession1_19_0.getProfileKey()when a chat session is configured, then reads the optional UUID (Types.OPTIONAL_UUID).
- Encryption Response salt/signature variant (1.19–1.19.2): when the client holds a profile key it replies to Encryption Request with
salt + signatureinstead 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_UPDATEpacket (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— serverboundHELLOwritesOPTIONAL_PROFILE_KEYfrom the chat session (or null) but the client no longer supplies one;:295-322handles the Encryption-Response salt/signature using the in-PlayChatSession1_19_1key.
Cross-reference: ../05-login-encryption.md lines 35–53 (Login Start
Signaturecontainer, present 1.19–1.19.2, removed 1.19.3) and 100–123 (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:
- Chat preview gone. All preview packets are dropped: ViaVersion cancels
CHAT_PREVIEW,SET_DISPLAY_CHAT_PREVIEW,PLAYER_CHAT_HEADER, andDELETE_CHATwhen translating a 1.19.1 server down:v1_19_1to1_19_3/Protocol1_19_1To1_19_3.java:325-328. The standalonePLAYER_CHAT_HEADERpacket (0x32 in 760) is gone from the 761 clientbound set. signedPreviewbool removed from serverboundCHAT/CHAT_COMMAND; the message format is acknowledgement-based instead.- Last-seen moves to an offset + bitset (
ACKNOWLEDGED_BIT_SET) rather than a full signature array on every message — far more compact: serverboundCHAT(0x05) ends withoffset VarInt+ACKNOWLEDGED_BIT_SET:Protocol1_19_1To1_19_3.java:230-270;CHAT_COMMAND(0x04) likewise:176-229. - 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. 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 signedPLAYER_CHAT. ViaVersion rewrites a 1.19.1PLAYER_CHATdown intoDISGUISED_CHAT:Protocol1_19_1To1_19_3.java:128-174.- 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 unlessenforce-secure-profileforces 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:
- 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_CHAT→SYSTEM_CHAT(Protocol1_19To1_19_1.java:85-111); 761→760 turnsPLAYER_CHAT→DISGUISED_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 serverboundCHAT/CHAT_COMMANDhandler).
- 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
- Risk: relaying stripped signatures to an
enforce-secure-profileserver. Ifenforce-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 ownChatSessionkey 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. enforcesSecureChatflag must be synthesised.SERVER_DATAgained theenforcesSecureChatbool at 760; a proxy bridging from an older server has to fabricate it (ViaVersion sources it fromVia.getConfig().enforceSecureChat():Protocol1_19To1_19_1.java:227).- Last-seen / acknowledgement bookkeeping is mandatory at 760+. Because the chain folds in last-seen signatures, a translating proxy must track received
PLAYER_CHATsignatures and emitCHAT_ACK/offset+bitset acknowledgements, or the server's chain validation desyncs (ViaVersion'sReceivedMessagesStorage, auto-CHAT_ACKat 64 unacked:Protocol1_19_1To1_19_3.java:135-148). - 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_UPDATEpath 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. - 762 is cheap. No signing change; a proxy only resets per-session chat state on Login and handles the
SERVER_DATAMOTD/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) andapi/.../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.4version.json+common/protocolVersions.json(protocol numbers + dataVersions). - Cross-link: ../05-login-encryption.md (profile keys in Login Start; Encryption Response salt/signature form).