Skip to main content

The DNSCrypt Protocol
draft-denis-dprive-dnscrypt-10

Document Type Active Internet-Draft (individual)
Author Frank Denis
Last updated 2026-06-20
RFC stream (None)
Intended RFC status (None)
Formats
Stream Stream state (No stream defined)
Consensus boilerplate Unknown
RFC Editor Note (None)
IESG IESG state I-D Exists
Telechat date (None)
Responsible AD (None)
Send notices to (None)
draft-denis-dprive-dnscrypt-10
Network Working Group                                           F. Denis
Internet-Draft                                    Individual Contributor
Intended status: Informational                              20 June 2026
Expires: 22 December 2026

                         The DNSCrypt Protocol
                     draft-denis-dprive-dnscrypt-10

Abstract

   The DNSCrypt protocol is designed to encrypt and authenticate DNS
   traffic between clients and resolvers.  This document specifies the
   protocol and its implementation, providing a standardized approach to
   securing DNS communications.  DNSCrypt improves confidentiality,
   integrity, and resistance to attacks affecting the original DNS
   protocol while maintaining compatibility with existing DNS
   infrastructure.

About This Document

   This note is to be removed before publishing as an RFC.

   The latest revision of this draft can be found at
   https://dnscrypt.github.io/dnscrypt-protocol/.  Status information
   for this document may be found at https://datatracker.ietf.org/doc/
   draft-denis-dprive-dnscrypt/.

   Source for this draft and an issue tracker can be found at
   https://github.com/DNSCrypt/dnscrypt-protocol.

Status of This Memo

   This Internet-Draft is submitted in full conformance with the
   provisions of BCP 78 and BCP 79.

   Internet-Drafts are working documents of the Internet Engineering
   Task Force (IETF).  Note that other groups may also distribute
   working documents as Internet-Drafts.  The list of current Internet-
   Drafts is at https://datatracker.ietf.org/drafts/current/.

   Internet-Drafts are draft documents valid for a maximum of six months
   and may be updated, replaced, or obsoleted by other documents at any
   time.  It is inappropriate to use Internet-Drafts as reference
   material or to cite them other than as "work in progress."

   This Internet-Draft will expire on 22 December 2026.

Denis                   Expires 22 December 2026                [Page 1]
Internet-Draft                  DNSCrypt                       June 2026

Copyright Notice

   Copyright (c) 2026 IETF Trust and the persons identified as the
   document authors.  All rights reserved.

   This document is subject to BCP 78 and the IETF Trust's Legal
   Provisions Relating to IETF Documents (https://trustee.ietf.org/
   license-info) in effect on the date of publication of this document.
   Please review these documents carefully, as they describe your rights
   and restrictions with respect to this document.

Table of Contents

   1.  Introduction  . . . . . . . . . . . . . . . . . . . . . . . .   3
   2.  Conventions And Definitions . . . . . . . . . . . . . . . . .   4
   3.  Protocol Flow . . . . . . . . . . . . . . . . . . . . . . . .   4
   4.  Protocol Components . . . . . . . . . . . . . . . . . . . . .   6
   5.  Protocol Description  . . . . . . . . . . . . . . . . . . . .   9
     5.1.  Overview  . . . . . . . . . . . . . . . . . . . . . . . .   9
     5.2.  Transport . . . . . . . . . . . . . . . . . . . . . . . .  10
     5.3.  Session Establishment . . . . . . . . . . . . . . . . . .  11
     5.4.  Query Processing  . . . . . . . . . . . . . . . . . . . .  11
       5.4.1.  Padding For Client Queries Over UDP . . . . . . . . .  11
       5.4.2.  Client Queries Over UDP . . . . . . . . . . . . . . .  12
       5.4.3.  Padding For Client Queries Over TCP . . . . . . . . .  13
       5.4.4.  Client Queries Over TCP . . . . . . . . . . . . . . .  13
     5.5.  Certificates  . . . . . . . . . . . . . . . . . . . . . .  14
   6.  Implementation Status . . . . . . . . . . . . . . . . . . . .  17
   7.  Security Considerations . . . . . . . . . . . . . . . . . . .  18
     7.1.  Protocol Security . . . . . . . . . . . . . . . . . . . .  18
     7.2.  Implementation Security . . . . . . . . . . . . . . . . .  18
     7.3.  Attack Mitigation . . . . . . . . . . . . . . . . . . . .  20
     7.4.  Privacy Considerations  . . . . . . . . . . . . . . . . .  20
     7.5.  Operational Security  . . . . . . . . . . . . . . . . . .  21
   8.  Operational Considerations  . . . . . . . . . . . . . . . . .  22
   9.  Anonymized DNSCrypt . . . . . . . . . . . . . . . . . . . . .  23
     9.1.  Protocol Overview . . . . . . . . . . . . . . . . . . . .  23
     9.2.  Client Queries  . . . . . . . . . . . . . . . . . . . . .  23
     9.3.  Relay Behavior  . . . . . . . . . . . . . . . . . . . . .  24
     9.4.  Operational Considerations  . . . . . . . . . . . . . . .  25
   10. Post-Quantum Key Exchange (PQDNSCrypt)  . . . . . . . . . . .  26
     10.1.  Key Encapsulation Instead of Key Agreement . . . . . . .  26
     10.2.  PQ Certificates  . . . . . . . . . . . . . . . . . . . .  27
     10.3.  Certificate Retrieval Amplification  . . . . . . . . . .  28
     10.4.  PQ Key Derivation  . . . . . . . . . . . . . . . . . . .  29
     10.5.  PQ Query and Response Format . . . . . . . . . . . . . .  30
     10.6.  Padding and Transport  . . . . . . . . . . . . . . . . .  31
     10.7.  Stateless Resumption . . . . . . . . . . . . . . . . . .  31

Denis                   Expires 22 December 2026                [Page 2]
Internet-Draft                  DNSCrypt                       June 2026

       10.7.1.  Issuing a Ticket . . . . . . . . . . . . . . . . . .  31
       10.7.2.  Resuming . . . . . . . . . . . . . . . . . . . . . .  32
       10.7.3.  Resumption Considerations  . . . . . . . . . . . . .  33
     10.8.  PQ and Anonymized DNSCrypt . . . . . . . . . . . . . . .  33
     10.9.  PQ Downgrade Protection  . . . . . . . . . . . . . . . .  34
     10.10. PQ Security Considerations . . . . . . . . . . . . . . .  34
   11. IANA Considerations . . . . . . . . . . . . . . . . . . . . .  35
   12. Appendix 1: The Box-XChaChaPoly Algorithm . . . . . . . . . .  35
     12.1.  Conventions and Definitions  . . . . . . . . . . . . . .  35
     12.2.  HChaCha20  . . . . . . . . . . . . . . . . . . . . . . .  35
     12.3.  Test Vector For The HChaCha20 Block Function . . . . . .  36
     12.4.  ChaCha20_DJB . . . . . . . . . . . . . . . . . . . . . .  36
     12.5.  XChaCha20_DJB  . . . . . . . . . . . . . . . . . . . . .  37
     12.6.  XChaCha20_DJB-Poly1305 . . . . . . . . . . . . . . . . .  37
     12.7.  The Box-XChaChaPoly Algorithm  . . . . . . . . . . . . .  38
   13. Appendix 2: DNSCrypt Test Vectors . . . . . . . . . . . . . .  38
     13.1.  Pinned Inputs  . . . . . . . . . . . . . . . . . . . . .  38
     13.2.  Public Keys and Shared Key . . . . . . . . . . . . . . .  40
     13.3.  Certificate  . . . . . . . . . . . . . . . . . . . . . .  40
     13.4.  Client Query (UDP) . . . . . . . . . . . . . . . . . . .  41
     13.5.  Server Response (UDP)  . . . . . . . . . . . . . . . . .  43
     13.6.  Padding and Transport Notes  . . . . . . . . . . . . . .  44
     13.7.  Negative Cases . . . . . . . . . . . . . . . . . . . . .  44
   14. Appendix 3: PQ Test Vector Structure  . . . . . . . . . . . .  45
     14.1.  Provisional Assignments  . . . . . . . . . . . . . . . .  45
     14.2.  Pinned Inputs  . . . . . . . . . . . . . . . . . . . . .  46
     14.3.  Profile Extension and Signature Input  . . . . . . . . .  47
     14.4.  Full X-Wing Query  . . . . . . . . . . . . . . . . . . .  49
     14.5.  Full Response and Ticket Issuance  . . . . . . . . . . .  50
     14.6.  Resumed Query and Response . . . . . . . . . . . . . . .  51
     14.7.  Negative Cases . . . . . . . . . . . . . . . . . . . . .  52
     14.8.  Generated Values . . . . . . . . . . . . . . . . . . . .  53
   15. Normative References  . . . . . . . . . . . . . . . . . . . .  55
   Author's Address  . . . . . . . . . . . . . . . . . . . . . . . .  56

1.  Introduction

   The Domain Name System (DNS) [RFC1035] is a critical component of
   Internet infrastructure, but its original design did not include
   security features to protect the confidentiality and integrity of
   queries and responses.  This fundamental security gap exposes DNS
   traffic to eavesdropping, tampering, and various attacks that can
   compromise user privacy and network security.

   To address these vulnerabilities, this document defines the DNSCrypt
   protocol, which encrypts and authenticates DNS queries and responses,
   providing strong confidentiality, integrity, and resistance to
   attacks affecting the original DNS protocol.  The protocol is

Denis                   Expires 22 December 2026                [Page 3]
Internet-Draft                  DNSCrypt                       June 2026

   designed to be lightweight, extensible, and simple to implement
   securely on top of existing DNS infrastructure, offering a practical
   solution for securing DNS communications without requiring
   significant changes to current systems.

   The following sections detail the protocol's design, starting with an
   overview of its operation and then progressing through the technical
   specifications needed for implementation.

2.  Conventions And Definitions

   The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT",
   "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and
   "OPTIONAL" in this document are to be interpreted as described in
   BCP 14 [RFC2119] [RFC8174] when, and only when, they appear in all
   capitals, as shown here.

3.  Protocol Flow

   The DNSCrypt protocol consists of two distinct phases:

   1.  *Initial Setup Phase* (one-time):

       *  The client requests the server's certificate

       *  The server responds with its certificate containing public
          keys

   2.  *Ongoing Communication Phase* (repeated as needed):

       *  The client sends encrypted DNS queries

       *  The server responds with encrypted DNS responses

   The following diagram illustrates the complete protocol flow:

Denis                   Expires 22 December 2026                [Page 4]
Internet-Draft                  DNSCrypt                       June 2026

   +--------+                    +--------+
   |        |                    |        |
   | Client |                    | Server |
   |        |                    |        |
   +--------+                    +--------+
       |                             |
       | 1. Request Certificate      |
       |---------------------------->|
       |                             |
       | 2. Certificate Response     |
       |<----------------------------|
       |                             |
       | 3. Encrypted Query          |
       |---------------------------->|
       |                             |
       | 4. Encrypted Query          |
       |---------------------------->|
       |                             |
       | 5. Encrypted Response       |
       |<----------------------------|
       |                             |
       | 6. Encrypted Response       |
       |<----------------------------|
       |                             |
       | 7. Encrypted Query          |
       |---------------------------->|
       |                             |
       | 8. Encrypted Response       |
       |<----------------------------|
       |                             |
       |                             |

   The initial setup phase (steps 1-2) occurs only when:

   *  A client first starts using a DNSCrypt server

   *  The client's cached certificate expires

   *  The client detects a certificate with a higher serial number

   After the initial setup, the client and server engage in the ongoing
   communication phase (steps 3-8), where encrypted queries and
   responses are exchanged as needed.  This phase can be repeated
   indefinitely until the certificate expires or a new certificate is
   available.

   The ongoing communication phase operates with several important
   characteristics that distinguish it from traditional DNS:

Denis                   Expires 22 December 2026                [Page 5]
Internet-Draft                  DNSCrypt                       June 2026

   1.  *Stateless Operation*: Each query and response is independent.
       The server does not maintain state between queries.

   2.  *Out-of-Order Responses*: Responses may arrive in a different
       order than the queries were sent.  Each response is self-
       contained and can be processed independently.

   3.  *Concurrent Queries*: A client can send multiple queries without
       waiting for earlier responses, and responses can be processed
       independently as they arrive.

   With this understanding of the protocol flow, we can now examine the
   specific components that make up DNSCrypt packets and their
   structure.

4.  Protocol Components

   The DNSCrypt protocol defines specific packet structures for both
   client queries and server responses.  These components work together
   to provide the security properties described in the previous section.

   Definitions for client queries:

   *  <dnscrypt-query>: <client-magic> <client-pk> <client-nonce>
      <encrypted-query>

   *  <client-magic>: an 8 byte identifier for the resolver certificate
      chosen by the client.

   *  <client-pk>: the client's public key, whose length depends on the
      encryption algorithm defined in the chosen certificate.

   *  <client-sk>: the client's secret key.

   *  <resolver-pk>: the resolver's public key.

   *  <client-nonce>: a unique query identifier for a given (<client-
      sk>, <resolver-pk>) tuple.  Every newly encrypted DNSCrypt query
      for the same (<client-sk>, <resolver-pk>) tuple MUST use a
      distinct <client-nonce> value, even when the plaintext DNS query
      is being retried.  Retransmitting the same already-encrypted
      DNSCrypt packet does not require changing its nonce.  The length
      of <client-nonce> is determined by the chosen encryption
      algorithm.

   *  AE: the authenticated encryption function.  For the encryption
      systems defined in this document, it is the XChaCha20_DJB-Poly1305
      construction of Appendix 1, whose output is the 16-byte

Denis                   Expires 22 December 2026                [Page 6]
Internet-Draft                  DNSCrypt                       June 2026

      authentication tag followed by the ciphertext.  This is the NaCl
      secretbox layout, in which the one-time Poly1305 key is taken from
      the start of the keystream; it is not the AEAD of [RFC8439], and
      the two are not interchangeable.  See Appendix 1 for details.

   *  <encrypted-query>: AE(<shared-key> <client-nonce> <client-nonce-
      pad>, <client-query> <client-query-pad>)

   *  <shared-key>: the shared key derived from <resolver-pk> and
      <client-sk>, using the key exchange algorithm defined in the
      chosen certificate.

   *  <client-query>: the unencrypted client query.  The query is not
      modified; in particular, the query flags are not altered.

   *  <client-nonce-pad>: <client-nonce> length is half the nonce length
      required by the encryption algorithm.  In client queries, the
      other half, <client-nonce-pad> is filled with NUL bytes.  For <es-
      version> 0x00 0x02, the encryption algorithm uses a 24-byte nonce,
      so <client-nonce> is 12 bytes and <client-nonce-pad> is the
      remaining 12 NUL bytes.

   *  <client-query-pad>: the variable-length padding.

   Definitions for server responses:

   *  <dnscrypt-response>: <resolver-magic> <nonce> <encrypted-response>

   *  <resolver-magic>: the 0x72 0x36 0x66 0x6e 0x76 0x57 0x6a 0x38 byte
      sequence

   *  <nonce>: <client-nonce> <resolver-nonce>

   *  <client-nonce>: the nonce sent by the client in the related query.

   *  <client-pk>: the client's public key.

   *  <resolver-sk>: the resolver's secret key.

   *  <resolver-nonce>: a unique response identifier for a given
      (<client-pk>, <resolver-sk>) tuple.  The length of <resolver-
      nonce> depends on the chosen encryption algorithm.

Denis                   Expires 22 December 2026                [Page 7]
Internet-Draft                  DNSCrypt                       June 2026

   *  AE: the authenticated encryption function.  For the encryption
      systems defined in this document, it is the XChaCha20_DJB-Poly1305
      construction of Appendix 1, whose output is the 16-byte
      authentication tag followed by the ciphertext.  This is the NaCl
      secretbox layout, in which the one-time Poly1305 key is taken from
      the start of the keystream; it is not the AEAD of [RFC8439], and
      the two are not interchangeable.  See Appendix 1 for details.

   *  <encrypted-response>: AE(<shared-key>, <nonce>, <resolver-
      response> <resolver-response-pad>)

   *  <shared-key>: the shared key derived from <resolver-sk> and
      <client-pk>, using the key exchange algorithm defined in the
      chosen certificate.

   *  <resolver-response>: the unencrypted resolver response.  The
      response is not modified; in particular, the query flags are not
      altered.

   *  <resolver-response-pad>: the variable-length padding.

   The following diagram shows the structure of a DNSCrypt query packet:

    0                   1                   2                   3
    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                         Client Magic                          |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                                                               |
   +                      Client Public Key                        +
   |                                                               |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                                                               |
   +                        Client Nonce                           +
   |                                                               |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                                                               |
   +                       Encrypted Query                         +
   |                                                               |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

   The following diagram shows the structure of a DNSCrypt response
   packet:

Denis                   Expires 22 December 2026                [Page 8]
Internet-Draft                  DNSCrypt                       June 2026

    0                   1                   2                   3
    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                        Resolver Magic                         |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                                                               |
   +                          Nonce                                +
   |                                                               |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                                                               |
   +                      Encrypted Response                       +
   |                                                               |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

   These packet structures form the foundation for the protocol
   operations described in the next section, which details how clients
   and servers use these components to establish secure communications.

5.  Protocol Description

5.1.  Overview

   Building on the protocol flow and components described earlier, this
   section provides a detailed examination of how the DNSCrypt protocol
   operates.  The protocol follows a well-defined sequence of steps:

   1.  The DNSCrypt client sends a DNS query to a DNSCrypt server to
       retrieve the server's public keys.

   2.  The client generates its own key pair.

   3.  The client encrypts unmodified DNS queries using a server's
       public key, padding them as necessary, and concatenates them to a
       nonce and a copy of the client's public key.  The resulting
       output is transmitted to the server via standard DNS transport
       mechanisms [RFC1035].

   4.  Encrypted queries are decrypted by the server using the attached
       client public key and the server's own secret key.  The output is
       a regular DNS packet that doesn't require any special processing.

   5.  To send an encrypted response, the server adds padding to the
       unmodified response, encrypts the result using the shared key and
       a nonce made of the client's nonce followed by the resolver's
       nonce, and truncates the response if necessary.  The resulting
       packet, truncated or not, is sent to the client using standard
       DNS mechanisms.

Denis                   Expires 22 December 2026                [Page 9]
Internet-Draft                  DNSCrypt                       June 2026

   6.  The client authenticates and decrypts the response using the
       shared key and the nonce included in the response.  If the
       response was truncated, the client MAY adjust internal parameters
       and retry over TCP [RFC7766].  If not, the output is a regular
       DNS response that can be directly forwarded to applications and
       stub resolvers.

   Key features of the DNSCrypt protocol include:

   *  Stateless operation: Every query can be processed independently
      from other queries, with no session identifiers required.

   *  Flexible key management: Clients can replace their keys whenever
      they want, without extra interactions with servers.

   *  Proxy support: DNSCrypt packets can securely be proxied without
      having to be decrypted, allowing client IP addresses to be hidden
      from resolvers ("Anonymized DNSCrypt").

   *  Shared infrastructure: Recursive DNS servers can accept DNSCrypt
      queries on the same IP address and port used for regular DNS
      traffic.

   *  Attack mitigation: DNSCrypt mitigates two common security
      vulnerabilities in regular DNS over UDP: amplification [RFC5358]
      and fragmentation attacks.

   These key features enable DNSCrypt to provide robust security while
   maintaining practical deployability.  The protocol's transport
   characteristics further support these goals.

5.2.  Transport

   The DNSCrypt protocol can use the UDP and TCP transport protocols.
   DNSCrypt clients and resolvers SHOULD support the protocol via UDP,
   and MUST support it over TCP.

   Both TCP and UDP connections using DNSCrypt SHOULD employ port 443 by
   default.

   The choice of port 443 helps DNSCrypt traffic blend with HTTPS
   traffic, providing some protection against traffic analysis.  Once
   transport is established, the next step is session establishment
   through certificate exchange.

Denis                   Expires 22 December 2026               [Page 10]
Internet-Draft                  DNSCrypt                       June 2026

5.3.  Session Establishment

   From the client's perspective, a DNSCrypt session is initiated when
   the client sends an unauthenticated DNS query to a DNSCrypt-capable
   resolver.  This DNS query contains encoded information about the
   certificate versions supported by the client and a public identifier
   of the desired provider.

   The resolver sends back a collection of signed certificates that the
   client MUST verify using the pre-distributed provider public key.
   Each certificate includes a validity period, a serial number, a
   version that defines a key exchange mechanism, an authenticated
   encryption algorithm and its parameters, as well as a short-term
   public key, known as the resolver public key.

   Resolvers have the ability to support various algorithms and can
   concurrently advertise multiple short-term public keys (resolver
   public keys).  The client picks the one with the highest serial
   number among the currently valid ones that match a supported protocol
   version.

   Every certificate contains a unique magic number that the client MUST
   include at the beginning of their queries.  This allows the resolver
   to identify which certificate the client selected for crafting a
   particular query.

   The encryption algorithm, resolver public key, and client magic
   number from the chosen certificate are then used by the client to
   send encrypted queries.  These queries include the client public key.

   With the knowledge of the chosen certificate and corresponding secret
   key, along with the client's public key, the resolver is able to
   verify, decrypt the query, and then encrypt the response utilizing
   identical parameters.

   Once the session is established through certificate exchange, the
   ongoing query processing follows specific rules for different
   transport protocols and padding requirements.

5.4.  Query Processing

5.4.1.  Padding For Client Queries Over UDP

   Before encryption takes place, queries are padded according to the
   ISO/IEC 7816-4 standard.  Padding begins with a single byte holding
   the value 0x80, followed by any number of NUL bytes.

Denis                   Expires 22 December 2026               [Page 11]
Internet-Draft                  DNSCrypt                       June 2026

   <client-query> <client-query-pad> MUST be at least <min-query-len>
   bytes.  In this context, <client-query> represents the original
   client query, while <client-query-pad> denotes the added padding.

   Should the client query's length fall short of <min-query-len> bytes,
   the padding length MUST be adjusted in order to satisfy the length
   requirement.

   <min-query-len> is a variable length, initially set to 256 bytes, and
   MUST be a multiple of 64 bytes.  It represents the minimum permitted
   length for a client query, inclusive of padding.

5.4.2.  Client Queries Over UDP

   UDP-based client queries need to follow the padding guidelines
   outlined in the previous section.

   Each UDP packet MUST hold one query, with the complete content
   comprising the <dnscrypt-query> structure specified in the Protocol
   Components section.

   UDP packets employing the DNSCrypt protocol have the capability to be
   split into distinct IP packets sharing the same source port.

   Upon receiving a query, the resolver may choose to either disregard
   it or send back a response encrypted using DNSCrypt.

   The client MUST authenticate and, if authentication succeeds, decrypt
   the response with the help of the resolver's public key, the shared
   secret, and the response nonce.  The client MUST verify that the
   <client-nonce> prefix of the response nonce matches a currently
   outstanding query.  In case the response fails verification, or the
   <client-nonce> does not match a currently outstanding query, it MUST
   be disregarded by the client.

   If the response has the TC flag set, the client MUST:

   1.  send the query again using TCP [RFC7766]

   2.  set the new minimum query length as:

   <min-query-len> ::= min(<min-query-len> + 64, <max-query-len>)

   <min-query-len> denotes the minimum permitted length for a client
   query, including padding.  That value MUST be capped so that the full
   length of a DNSCrypt packet doesn't exceed the maximum size required
   by the transport layer.

Denis                   Expires 22 December 2026               [Page 12]
Internet-Draft                  DNSCrypt                       June 2026

   The client MAY decrease <min-query-len>, but the length MUST remain a
   multiple of 64 bytes.

   While UDP queries require careful length management due to truncation
   concerns, TCP queries follow different padding rules due to the
   reliable nature of the transport.

5.4.3.  Padding For Client Queries Over TCP

   Queries MUST undergo padding using the ISO/IEC 7816-4 format before
   being encrypted.  The padding starts with a byte valued 0x80 followed
   by a variable number of NUL bytes.

   The length of <client-query-pad> is selected randomly, ranging from 1
   to 256 bytes, including the initial byte valued at 0x80.  The total
   length of <client-query> <client-query-pad> MUST be a multiple of 64
   bytes.

   For example, an originally unpadded 56-bytes DNS query can be padded
   as:

   <56-bytes-query> 0x80 0x00 0x00 0x00 0x00 0x00 0x00 0x00

   or

   <56-bytes-query> 0x80 (0x00 * 71)

   or

   <56-bytes-query> 0x80 (0x00 * 135)

   or

   <56-bytes-query> 0x80 (0x00 * 199)

5.4.4.  Client Queries Over TCP

   The sole differences between encrypted client queries transmitted via
   TCP and those sent using UDP lie in the padding length calculation
   and the inclusion of a two-byte big-endian length prefix for the
   encrypted DNSCrypt packet.

   Unlike UDP queries, a query sent over TCP can be shorter than the
   response.

Denis                   Expires 22 December 2026               [Page 13]
Internet-Draft                  DNSCrypt                       June 2026

   After having received a response from the resolver, the client and
   the resolver MUST close the TCP connection to ensure security and
   comply with this revision of the protocol, which prohibits multiple
   transactions over the same TCP connection.

   The query processing rules described above depend on the certificate
   information obtained during session establishment.  The certificate
   format and management procedures are critical to the protocol's
   security.

5.5.  Certificates

   The following diagram shows the structure of a DNSCrypt certificate:

    0                   1                   2                   3
    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                         Cert Magic                            |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |    ES Version    |  Protocol Minor Version    |   Reserved    |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                                                               |
   +                         Signature                             +
   |                                                               |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                                                               |
   +                      Resolver Public Key                      +
   |                                                               |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                        Client Magic                           |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                          Serial                               |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                          TS Start                             |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                          TS End                               |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                                                               |
   +                        Extensions                             +
   |                                                               |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

   To initiate a DNSCrypt session, a client transmits an ordinary
   unencrypted TXT DNS query to the resolver's IP address and DNSCrypt
   port.  The attempt is first made using UDP; if unsuccessful due to
   failure, timeout, or truncation, the client then proceeds with TCP.

   Resolvers are not required to serve certificates both on UDP and TCP.

Denis                   Expires 22 December 2026               [Page 14]
Internet-Draft                  DNSCrypt                       June 2026

   The name in the question (<provider name>) MUST follow this scheme:

   <protocol-major-version> . dnscrypt-cert . <zone>

   A major protocol version has only one certificate format.

   A DNSCrypt client implementing the second version of the protocol
   MUST send a query with the TXT type and IN class, and a name of the
   form:

   2.dnscrypt-cert.example.com

   The RD (Recursion Desired) bit MAY be set; a resolver serving a
   certificate for its own provider name ignores it.

   The zone MUST be a valid DNS name, but MAY not be registered in the
   DNS hierarchy.

   A single provider name can be shared by multiple resolvers operated
   by the same entity, and a resolver can respond to multiple provider
   names, especially to support multiple protocol versions
   simultaneously.

   In order to use a DNSCrypt-enabled resolver, a client must know the
   following information:

   *  The resolver IP address and port

   *  The provider name

   *  The provider public key

   The provider public key is a long-term key whose sole purpose is to
   verify the certificates.  It is never used to encrypt or verify DNS
   queries.  A single provider public key can be employed to sign
   multiple certificates.

   For example, an organization operating multiple resolvers can use a
   unique provider name and provider public key across all resolvers,
   and just provide a list of IP addresses and ports.  Each resolver MAY
   have its unique set of certificates that can be signed with the same
   key.

Denis                   Expires 22 December 2026               [Page 15]
Internet-Draft                  DNSCrypt                       June 2026

   It is RECOMMENDED that certificates are signed using specialized
   hardware rather than directly on the resolvers themselves.  Once
   signed, resolvers SHOULD make these certificates available to
   clients.  Signing certificates on dedicated hardware helps ensure
   security and integrity, as it isolates the process from potential
   vulnerabilities present in the resolver's system.

   A successful response to a certificate request contains one or more
   TXT records, each record containing a certificate encoded as follows:

   *  <cert>: <cert-magic> <es-version> <protocol-minor-version>
      <signature> <resolver-pk> <client-magic> <serial> <ts-start> <ts-
      end> <extensions>

   *  <cert-magic>: 0x44 0x4e 0x53 0x43

   *  <es-version>: the cryptographic construction to use with this
      certificate.  For the Box-XChaChaPoly construction of Appendix 1,
      that is, the X25519 key exchange with the XChaCha20_DJB-Poly1305
      authenticated encryption algorithm, <es-version> MUST be 0x00
      0x02.

   *  <protocol-minor-version>: 0x00 0x00

   *  <signature>: a 64-byte signature of (<resolver-pk> <client-magic>
      <serial> <ts-start> <ts-end> <extensions>) using the Ed25519
      algorithm and the provider secret key.  Ed25519 MUST be used in
      this version of the protocol.

   *  <resolver-pk>: the resolver short-term public key, which is 32
      bytes when using X25519.

   *  <client-magic>: The first 8 bytes of a client query that was built
      using the information from this certificate.  It MAY be a
      truncated public key.  Two valid certificates cannot share the
      same <client-magic>. <client-magic> MUST NOT start with 0x00 0x00
      0x00 0x00 0x00 0x00 0x00 (seven all-zero bytes) in order to avoid
      confusion with the QUIC protocol [RFC9000].

   *  <serial>: a 4-byte serial number in big-endian format.  If more
      than one certificate is valid, the client MUST prefer the
      certificate with a higher serial number.

   *  <ts-start>: the date the certificate is valid from, as a big-
      endian 4-byte unsigned Unix timestamp.

   *  <ts-end>: the date the certificate is valid until (inclusive), as
      a big-endian 4-byte unsigned Unix timestamp.

Denis                   Expires 22 December 2026               [Page 16]
Internet-Draft                  DNSCrypt                       June 2026

   *  <extensions>: empty in the current protocol version, but may
      contain additional data in future revisions, including minor
      versions.  The computation and verification of the signature MUST
      include the extensions.  An implementation not supporting these
      extensions MUST ignore them.

   Certificates made of this information, without extensions, are 116
   bytes long.  With the addition of <cert-magic>, <es-version>, and
   <protocol-minor-version>, the record is 124 bytes long.

   Within a TXT record, the certificate is carried in the record's
   RDATA, which is a sequence of length-prefixed character-strings
   [RFC1035].  A client reconstructs the certificate by concatenating
   these character-strings in order, after removing the single length
   octet that precedes each one.  A 124-byte classical certificate fits
   in a single character-string, whereas a larger certificate spans
   several character-strings that MUST be concatenated in this way
   before the certificate is parsed.

   After receiving a set of certificates, the client checks their
   validity based on the current date, filters out the ones designed for
   encryption systems that are not supported by the client, and chooses
   the certificate with the higher serial number.

   DNSCrypt queries sent by the client MUST use the <client-magic>
   header of the chosen certificate, as well as the specified encryption
   system and public key.

   The client MUST check for new certificates every hour and switch to a
   new certificate if:

   *  The current certificate is not present or not valid anymore,

   or

   *  A certificate with a higher serial number than the current one is
      available.

   The certificate management system ensures that cryptographic keys
   remain fresh and that clients can smoothly transition to updated
   certificates.  With the core protocol mechanics now established, we
   can examine implementation considerations.

6.  Implementation Status

   _Note: This section is to be removed before publishing as an RFC._

Denis                   Expires 22 December 2026               [Page 17]
Internet-Draft                  DNSCrypt                       June 2026

   Multiple implementations of the protocol described in this document
   have been developed and verified for interoperability.  A
   comprehensive list of known implementations can be found at
   https://dnscrypt.info/implementations.

   The successful deployment of multiple interoperable implementations
   demonstrates the protocol's maturity.  However, proper implementation
   requires careful attention to security considerations.

7.  Security Considerations

   This section discusses security considerations for the DNSCrypt
   protocol.

7.1.  Protocol Security

   The DNSCrypt protocol provides several security benefits:

   1.  *Confidentiality*: DNS queries and responses are encrypted using
       XChaCha20-Poly1305 [RFC8439], preventing eavesdropping of DNS
       traffic.  For example, a query for "example.com" would be
       encrypted and appear as random data to an observer.

   2.  *Integrity*: Message authentication using Poly1305 [RFC8439]
       ensures that responses cannot be tampered with in transit.  Any
       modification to the encrypted response would be detected and
       rejected by the client.

   3.  *Authentication*: The use of X25519 [RFC7748] for key exchange
       and Ed25519 for certificate signatures provides strong
       authentication of resolvers.  Clients can verify they are
       communicating with the intended resolver and not an impostor.

   4.  *Short-Term Resolver Keys*: Resolver certificates carry short-
       term public keys, limiting the impact of key compromise and
       enabling regular key rotation.

   These fundamental security properties depend on correct
   implementation practices.  Several implementation-specific security
   aspects require particular attention.

7.2.  Implementation Security

   Implementations should consider the following security aspects:

   1.  *Key Management*:

Denis                   Expires 22 December 2026               [Page 18]
Internet-Draft                  DNSCrypt                       June 2026

       *  Resolvers MUST rotate their short-term key pairs at most every
          24 hours

       *  Previous secret keys MUST be discarded after rotation

       *  Provider secret keys used for certificate signing SHOULD be
          stored in hardware security modules (HSMs)

       *  Example: A resolver might generate new key pairs daily at
          midnight UTC

   2.  *Nonce Management*:

       *  Nonces MUST NOT be reused for a given shared secret

       *  Clients SHOULD generate <client-nonce> values using either a
          cryptographically secure random number generator with enough
          entropy to make collisions negligible for the lifetime of the
          shared secret, or a secret-key pseudorandom construction, such
          as encrypting a timestamp and any counter or random bits
          needed for uniqueness using a block cipher.  Such
          constructions MUST NOT expose wall-clock time or other stable
          client state to observers, and MUST still provide nonce
          uniqueness for the lifetime of the shared secret

       *  Clients SHOULD NOT include unencrypted timestamps in nonce
          values, especially when Anonymized DNSCrypt, Tor, SOCKS, or
          other proxying or anonymity systems are used

       *  Clients can quickly discard stale responses by keeping local
          state for outstanding queries, including their <client-nonce>
          values and expiration deadlines, and rejecting responses for
          nonces that are unknown, already answered, or expired

   3.  *Padding*:

       *  Implementations MUST use the specified padding scheme to
          prevent traffic analysis

       *  The minimum query length SHOULD be adjusted based on network
          conditions

       *  Example: A 50-byte query might be padded to 256 bytes to
          prevent size-based fingerprinting

   4.  *Certificate Validation*:

Denis                   Expires 22 December 2026               [Page 19]
Internet-Draft                  DNSCrypt                       June 2026

       *  Clients MUST verify certificate signatures using the
          provider's public key

       *  Certificates MUST be checked for validity periods

       *  Clients MUST prefer certificates with higher serial numbers

       *  Example: A client might cache valid certificates and check for
          updates hourly

   Proper implementation of these security measures provides the
   foundation for the protocol's attack mitigation capabilities.

7.3.  Attack Mitigation

   DNSCrypt provides protection against several types of attacks:

   1.  *DNS Spoofing*: The use of authenticated encryption prevents
       spoofed responses.  An attacker cannot forge responses without
       the server's secret key.

   2.  *Amplification Attacks*: The padding requirements and minimum
       query length help prevent amplification attacks [RFC5358].  For
       example, a 256-byte minimum query size limits the amplification
       factor.

   3.  *Fragmentation Attacks*: The protocol mitigates fragmentation
       risks by padding queries, allowing truncated UDP responses, and
       retrying over TCP when necessary.

   4.  *Replay Exposure Reduction*: Nonces make responses query-
       specific.  Clients can discard late, duplicate, or unrelated
       responses by checking the returned <client-nonce> against local
       outstanding-query state and request deadlines, without exposing
       timestamps in plaintext nonce values.

   While DNSCrypt effectively mitigates these attacks, implementers
   should also be aware of privacy considerations that extend beyond
   basic protocol security.

7.4.  Privacy Considerations

   While DNSCrypt encrypts DNS traffic, there are some privacy
   considerations:

   1.  *Resolver Knowledge*: Resolvers can still see the client's IP
       address unless Anonymized DNSCrypt is used.  This can reveal the
       client's location and network.

Denis                   Expires 22 December 2026               [Page 20]
Internet-Draft                  DNSCrypt                       June 2026

   2.  *Query Patterns*: Even with encryption, the size and timing of
       queries may reveal information.  Padding helps mitigate this but
       doesn't eliminate it completely.

   3.  *Nonce Metadata*: Client nonces are visible on the wire.
       Encoding unencrypted timestamps or other stable or linkable state
       in nonce values can disclose clock information or support client
       fingerprinting, particularly when relays or anonymity networks
       are used.  Clients SHOULD prefer nonce constructions that reveal
       no wall-clock time to observers.

   4.  *Certificate Requests*: Initial certificate requests are
       unencrypted but only return public data.  This is a one-time
       exposure per session.

   These privacy considerations complement the security measures and
   should inform operational practices for DNSCrypt deployments.

7.5.  Operational Security

   Operators should consider:

   1.  *Key Distribution*: Provider public keys should be distributed
       securely to clients.  This might involve:

       *  Publishing keys on secure websites

       *  Using DNSSEC-signed records

       *  Including keys in software distributions

   2.  *Certificate Management*: Certificates should be signed on
       dedicated hardware, not on resolvers.  This provides:

       *  Better key protection

       *  Centralized certificate management

       *  Reduced attack surface

   3.  *Access Control*: Resolvers may implement access control based on
       client public keys.  This can:

       *  Prevent abuse

       *  Enable service differentiation

       *  Support business models

Denis                   Expires 22 December 2026               [Page 21]
Internet-Draft                  DNSCrypt                       June 2026

   4.  *Monitoring*: Operators should monitor for unusual patterns that
       may indicate attacks:

       *  High query rates from single clients

       *  Unusual query patterns

       *  Certificate request anomalies

   These operational security practices work together with the technical
   security measures to provide comprehensive protection.  Additional
   operational considerations extend beyond security to include
   practical deployment aspects.

8.  Operational Considerations

   Special attention should be paid to the uniqueness of the generated
   secret keys.

   Client public keys can be used by resolvers to authenticate clients,
   link queries to customer accounts, and unlock business-specific
   features such as redirecting specific domain names to a sinkhole.

   Resolvers accessible from any client IP address can also opt for only
   responding to a set of whitelisted public keys.

   Resolvers accepting queries from any client MUST accept any client
   public key.  In particular, an anonymous client can generate a new
   key pair for every session, or even for every query.  This mitigates
   the ability for a resolver to group queries by client public keys and
   discover the set of IP addresses a user might have been operating.

   Resolvers MUST rotate the short-term key pair every 24 hours at most,
   and MUST throw away the previous secret key.  After a key rotation, a
   resolver MUST still accept all the previous keys that haven't
   expired.

   Provider public keys MAY be published as DNSSEC-signed TXT records
   [RFC1035], in the same zone as the provider name.  For example, a
   query for the TXT type on the name "2.pubkey.example.com" may return
   a signed record containing a hexadecimal-encoded provider public key
   for the provider name "2.dnscrypt-cert.example.com".

   As a client is likely to reuse the same key pair many times, servers
   are encouraged to cache shared keys instead of performing the X25519
   operation for each query.  This makes the computational overhead of
   DNSCrypt negligible compared to plain DNS.

Denis                   Expires 22 December 2026               [Page 22]
Internet-Draft                  DNSCrypt                       June 2026

   While DNSCrypt provides strong encryption and authentication, some
   use cases require additional privacy protection.  The Anonymized
   DNSCrypt extension addresses scenarios where hiding client IP
   addresses from resolvers is necessary.

9.  Anonymized DNSCrypt

   While DNSCrypt encrypts DNS traffic, DNS server operators can still
   observe client IP addresses.  Anonymized DNSCrypt is an extension to
   the DNSCrypt protocol that allows queries and responses to be relayed
   by an intermediate server, hiding the client's IP address from the
   resolver.

   This extension maintains all the security properties of standard
   DNSCrypt while adding an additional layer of privacy protection.

9.1.  Protocol Overview

   Anonymized DNSCrypt works by having the client send encrypted queries
   to a relay server, which then forwards them to the actual DNSCrypt
   resolver.  The relay server cannot decrypt the queries or responses,
   and the resolver only sees the relay's IP address.

[Client]----(encrypted query)--->[Relay]----(encrypted query)--->[Server]

[Client]<--(encrypted response)--[Relay]<--(encrypted response)--[Server]

   Key properties of Anonymized DNSCrypt:

   *  The relay cannot decrypt or modify queries and responses

   *  The resolver only sees the relay's IP address, not the client's

   *  A DNSCrypt server can simultaneously act as a relay

   *  The protocol works over both UDP and TCP

9.2.  Client Queries

   The following diagram shows the structure of an Anonymized DNSCrypt
   query packet:

Denis                   Expires 22 December 2026               [Page 23]
Internet-Draft                  DNSCrypt                       June 2026

    0                   1                   2                   3
    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                         Anon Magic                            |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                                                               |
   +                        Server IP (IPv6)                       +
   |                                                               |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |        Server Port        |                                   |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+                                   +
   |                                                               |
   +                     DNSCrypt Query                            +
   |                                                               |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

   An Anonymized DNSCrypt query is a standard DNSCrypt query prefixed
   with information about the target server:

<anondnscrypt-query> ::= <anon-magic> <server-ip> <server-port> <dnscrypt-query>

   Where:

   *  <anon-magic>: 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0x00 0x00

   *  <server-ip>: 16 bytes encoded IPv6 address (IPv4 addresses are
      mapped to IPv6 using ::ffff:<ipv4 address> [RFC4291])

   *  <server-port>: 2 bytes in big-endian format

   *  <dnscrypt-query>: standard DNSCrypt query

   For example, a query for a server at 192.0.2.1:443 would be prefixed
   with:

   0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0x00 0x00
   0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
   0xff 0xff 0xc0 0x00 0x02 0x01 0x01 0xbb

9.3.  Relay Behavior

   Relays MUST:

   1.  Accept queries over both TCP and UDP

   2.  Communicate with upstream servers over UDP, even if client
       queries were sent over TCP

Denis                   Expires 22 December 2026               [Page 24]
Internet-Draft                  DNSCrypt                       June 2026

   3.  Validate incoming packets:

       *  Check that the target IP is not in a private range [RFC1918]

       *  Verify the port number is in an allowed range

       *  Ensure the DNSCrypt query doesn't start with <anon-magic>

       *  Verify the query doesn't start with 7 zero bytes (to avoid
          confusion with QUIC [RFC9000])

   4.  Forward valid queries unmodified to the server

   5.  Verify server responses:

       *  For encrypted DNSCrypt responses, check that the response is
          smaller than the query

       *  For certificate responses, check that the response transaction
          ID and query name match the relayed certificate query

       *  Validate the response format (either starts with resolver
          magic or is a certificate response)

       *  Forward valid responses unmodified to the client

   These relay requirements ensure that anonymization does not
   compromise the security properties of the underlying DNSCrypt
   protocol.  Proper deployment requires additional operational
   considerations.

9.4.  Operational Considerations

   When using Anonymized DNSCrypt:

   1.  Clients should choose relays and servers operated by different
       entities

   2.  Having relays and servers on different networks is recommended

   3.  Relay operators should:

       *  Refuse forwarding to reserved IP ranges [RFC1918]

       *  Restrict allowed server ports (typically only allowing port
          443)

       *  Monitor for abuse

Denis                   Expires 22 December 2026               [Page 25]
Internet-Draft                  DNSCrypt                       June 2026

   These operational guidelines help ensure that Anonymized DNSCrypt
   deployments provide the intended privacy benefits while maintaining
   security and preventing abuse.

10.  Post-Quantum Key Exchange (PQDNSCrypt)

   The key exchange described so far relies on X25519 [RFC7748], which a
   sufficiently capable quantum computer would be able to break.  An
   attacker who records DNSCrypt traffic today could therefore decrypt
   it once such a computer exists.  This section defines the PQ
   extension, which performs the key exchange with a hybrid post-quantum
   mechanism while leaving the rest of the protocol unchanged: the
   certificate system, the packet framing, the authenticated encryption,
   the nonces, the padding philosophy, and Anonymized DNSCrypt all carry
   over.  A DNSCrypt deployment that uses this extension is referred to
   as PQDNSCrypt.

   PQ is introduced as a new encryption system version (<es-version>
   0x00 0x03) inside the existing version 2 certificate format.  It is
   not a new major protocol version, and it does not change the
   certificate lookup name.  A resolver MAY advertise a PQ certificate
   alongside a classical certificate under the same provider name, and a
   client that does not implement PQ ignores the <es-version> value it
   does not recognize, exactly as already required.

   The values that PQ introduces, namely the <es-version>, the resume
   magic, the ticket parameters, and the profile identifiers, are
   provisional assignments used by this document and by the test vectors
   in Appendix 3.  They are expected to be confirmed before publication.

10.1.  Key Encapsulation Instead of Key Agreement

   Classical DNSCrypt uses a non-interactive key agreement: the resolver
   publishes an X25519 public key in its certificate, the client places
   its own X25519 public key in <client-pk>, and both sides compute the
   same shared secret.  A post-quantum key encapsulation mechanism (KEM)
   does not offer a non-interactive key agreement, but it fits the same
   single-message exchange.  The resolver generates a KEM key pair and
   publishes the public (encapsulation) key in the <resolver-pk> field
   of its certificate.  The client runs the KEM encapsulation against
   <resolver-pk>, obtaining a ciphertext and a shared secret, and places
   the ciphertext in the <client-pk> field.  The resolver runs the KEM
   decapsulation on the received ciphertext using its secret key and
   recovers the same shared secret.

   The <client-pk> field therefore carries a KEM ciphertext rather than
   a client public key, and its length is determined by the <es-version>
   of the chosen certificate.  The resolver remains stateless: it

Denis                   Expires 22 December 2026               [Page 26]
Internet-Draft                  DNSCrypt                       June 2026

   derives the shared secret from a single client message, with no per-
   client state.  Everything after the shared secret is shared with
   classical DNSCrypt, with the small additions described in this
   section.

   PQ uses X-Wing [I-D.connolly-cfrg-xwing-kem], a hybrid KEM that
   combines ML-KEM-768 [FIPS203] with X25519 [RFC7748].  Being a hybrid,
   the shared secret remains secure as long as either ML-KEM-768 or
   X25519 is unbroken, which protects against both a future quantum
   break of X25519 and an unexpected weakness in ML-KEM-768.  The X-Wing
   encapsulation key is 1216 bytes, the ciphertext is 1120 bytes, and
   the shared secret is 32 bytes.

10.2.  PQ Certificates

   A PQ certificate uses the version 2 certificate format without
   modification.  The <es-version> field is 0x00 0x03, and the
   <resolver-pk> field holds the 1216-byte X-Wing encapsulation key.
   The <client-magic> retains its role as a unique 8-byte certificate
   selector that MUST NOT begin with seven zero bytes; because a KEM
   ciphertext is freshly generated for every query, <client-magic> is an
   opaque identifier and is never a truncated public key.

   The Ed25519 signature input is unchanged.  It MUST cover (<resolver-
   pk> <client-magic> <serial> <ts-start> <ts-end> <extensions>),
   exactly as for classical certificates, so that provider signing
   procedures do not require a second code path.  The PQ profile
   metadata is authenticated through the already-signed <extensions>
   field, which for a PQ certificate contains a profile extension:

   <pq-profile-ext> ::= "PQD" <ext-version> <es-version> <kdf-id>
                         <aead-id> <resolver-pk-len> <client-kex-len>

   where <ext-version> is 0x01, <kdf-id> is 0x01 for HKDF-SHA256
   [RFC5869], <aead-id> is 0x01 for the XChaCha20_DJB-Poly1305
   construction of Appendix 1, and <resolver-pk-len> and <client-kex-
   len> are the encapsulation-key and ciphertext lengths as two-byte
   big-endian integers.  A client implementing PQ MUST require this
   extension on a PQ certificate and MUST reject the certificate if the
   <es-version> on the wire or the field lengths disagree with the
   signed copies in the extension.

Denis                   Expires 22 December 2026               [Page 27]
Internet-Draft                  DNSCrypt                       June 2026

   The existing signature already provides implicit integrity for <es-
   version>, because <resolver-pk> is signed and its length follows from
   <es-version>: altering the version shifts the signed region and
   causes verification to fail, as long as distinct encryption systems
   use distinct <resolver-pk> lengths.  The signed profile extension
   makes the binding explicit and preserves it should a future
   encryption system reuse an existing length.

   PQ certificates are approximately 1.3 KB before DNS TXT record
   framing.  A single 1320-byte PQ certificate requires six TXT
   character-strings and therefore about 1338 bytes as one answer RR; a
   response containing one classical 124-byte certificate and one PQ
   certificate is typically about 1.5 KB, depending on the provider
   name.  A response with two classical certificates and two PQ
   certificates is typically about 3.0 KB.  A response with one
   classical certificate and three PQ certificates will normally exceed
   4096 octets.

   A certificate response that contains a PQ certificate exceeds 512
   bytes, so retrieval relies on a sufficiently padded UDP query, TCP
   [RFC7766], or both.  A 4096-octet DNS response is a useful upper
   bound for certificate responses, but it is not a fragmentation-
   avoidance target.  Operators that need maximum UDP robustness SHOULD
   use a lower target; however, one PQ certificate already exceeds the
   common 1232-octet UDP payload target once DNS framing is included.

10.3.  Certificate Retrieval Amplification

   Certificate retrieval is an unauthenticated DNS query, and PQ
   certificates are much larger than classical ones, so a resolver that
   returns one or more PQ certificates over UDP could be abused as a
   traffic amplifier in response to queries with a spoofed source
   address.  The anti-amplification rule is the same one classical
   DNSCrypt already applies to its traffic: over UDP, a certificate
   response MUST NOT be larger than the request that triggered it.  A
   client that wants a PQ certificate over UDP therefore pads its
   certificate query to at least the size of the expected response,
   exactly as a query that carries a ciphertext is already large enough
   to cover its response.  The advertised EDNS(0) [RFC6891] UDP payload
   size is a fragmentation-avoidance hint and MUST NOT be used as the
   amplification limit, because a spoofed query can advertise an
   arbitrarily large buffer while remaining small.

   To let a client size that padding, a resolver SHOULD keep its UDP
   certificate set small and predictable.  For relay-compatible PQ
   deployments, resolvers SHOULD use the following certificate sets:

Denis                   Expires 22 December 2026               [Page 28]
Internet-Draft                  DNSCrypt                       June 2026

   *  Normal steady state: one classical certificate and one PQ
      certificate, typically about 1.5 KB.

   *  Rollover window: two classical certificates and two PQ
      certificates, typically about 3.0 KB.

   Resolvers SHOULD NOT exceed the rollover certificate set over UDP,
   and SHOULD keep certificate responses below 4096 octets.

   A client cannot tell from outside whether a rotation is in progress,
   so a client that wants PQ certificates over UDP across rotations
   SHOULD pad its certificate query to cover the rollover set rather
   than only the steady-state set; otherwise it silently drops to the
   classical certificate for the duration of every rollover window.

   If the certificate response would be larger than the request, the
   resolver MUST NOT return the oversized response over UDP; it SHOULD
   instead set the TC flag and rely on the client retrying over TCP
   [RFC7766].  When it does so, the resolver SHOULD still include the
   smaller classical certificate in the truncated UDP response, so that
   a client that does not implement PQ can proceed without a second
   round trip while a PQ-capable client learns that more certificates
   may be available over TCP.  Operators MAY serve a small classical
   certificate over UDP while requiring a padded query or TCP for larger
   PQ certificate sets.  This affects neither the certificate format nor
   the lookup name.

   Because a resolver can withhold PQ certificates from a truncated UDP
   response, a PQ-capable client MUST honor the TC flag on a certificate
   response and retry the certificate query over TCP when TCP to the
   resolver is available.  A client that ignores the TC flag would only
   ever observe the certificates present in the UDP response and could
   silently fail to use PQ even against a resolver that supports it.

10.4.  PQ Key Derivation

   For a PQ query, the shared secret is the 32-byte X-Wing shared
   secret.  It is not used directly as the encryption key.  Instead,
   both parties derive <shared-key> with HKDF-SHA256 [RFC5869], binding
   the certificate context and the ciphertext:

Denis                   Expires 22 December 2026               [Page 29]
Internet-Draft                  DNSCrypt                       June 2026

   cert-context ::= "DNSCrypt-PQ-v1" <es-version>
                    <protocol-minor-version> <resolver-pk>
                    <client-magic> <serial> <ts-start> <ts-end>
                    <extensions>

   <shared-key> ::= HKDF-SHA256(IKM  = <kem-ss>,
                                salt = <es-version> <client-magic>,
                                info = cert-context <client-kex>,
                                L    = 32)

   where <kem-ss> is the X-Wing shared secret and <client-kex> is the
   content of the <client-pk> field, that is, the X-Wing ciphertext.
   Binding <es-version> and <client-magic> separates keys across
   certificates and any future encryption system; binding <resolver-pk>,
   <serial>, and the validity timestamps ties the key to the exact
   signed certificate; binding <client-kex> ties it to the precise
   encapsulation.  The derived <shared-key> is then used with the
   XChaCha20_DJB-Poly1305 construction of Appendix 1 and the existing
   24-byte nonce construction: 12 client-chosen bytes followed by 12
   zero bytes for queries, and 12 client-chosen bytes followed by 12
   resolver-chosen bytes for responses.

10.5.  PQ Query and Response Format

   A PQ query uses the <dnscrypt-query> structure without modification;
   only the length of <client-pk> changes, to the 1120-byte X-Wing
   ciphertext.

   A PQ response uses the <dnscrypt-response> structure without
   modification on the wire, but the decrypted payload begins with a
   short control block ahead of the unmodified DNS response:

   <pq-response-plain> ::= <control-len> <control>
                            <resolver-response> <resolver-response-pad>

   <control-len> is a two-byte big-endian length.  When it is zero,
   <control> is absent and the DNS response begins immediately after the
   length.  When it is nonzero, <control> carries PQ control data,
   currently a stateless resumption ticket.  The response padding is
   computed over the whole <control-len> <control> <resolver-response>
   plaintext, so the decrypted payload keeps the usual length alignment.
   A PQ client removes the control block after decryption and forwards
   the unmodified DNS response.  Because the shared secret is symmetric,
   the resolver needs no additional KEM operation to encrypt a response.

Denis                   Expires 22 December 2026               [Page 30]
Internet-Draft                  DNSCrypt                       June 2026

10.6.  Padding and Transport

   A PQ query that carries a ciphertext includes roughly 1.1 KB in
   <client-pk>, so it is always far larger than its response.  The
   minimum query length defined for client queries over UDP exists to
   prevent amplification, and that concern does not apply to a query
   that is already this large.  A PQ query that carries a ciphertext is
   therefore NOT subject to the 256-byte minimum: it is padded only to
   the next multiple of 64 bytes.  A resumed query, described below,
   carries a small ticket instead of a ciphertext, so it MUST use the
   regular minimum query length, initially 256 bytes, as in classical
   DNSCrypt.

   PQ queries and responses MUST be supported over TCP [RFC7766], and
   TCP is a first-class transport for PQ rather than only a fallback.  A
   client SHOULD use a configurable UDP payload size target, 1232 bytes
   by default, and SHOULD use TCP when a query would exceed it.  A PQ
   query that carries a ciphertext is approximately 1220 bytes, which
   fits within a single unfragmented datagram on common paths but can
   exceed 1232 bytes once the Anonymized DNSCrypt prefix is added; in
   that case the client SHOULD use TCP.

10.7.  Stateless Resumption

   Performing a KEM decapsulation for every query is significantly more
   expensive than the classical X25519 operation, and because every
   ciphertext is distinct there is no shared-key cache to amortize it.
   To control this cost without keeping per-client state, PQ defines
   stateless resumption: after an initial query that carries a
   ciphertext, the resolver issues an opaque ticket that lets subsequent
   queries skip both the ciphertext and the decapsulation.

   A resolver maintains one or more server-wide ticket keys, denoted TK.
   A TK is shared by all processes answering for a given certificate and
   is rotated independently of client traffic.  A resolver MAY retain a
   previous TK for a short overlap so that outstanding tickets continue
   to verify, but the overlap MUST NOT exceed the advertised ticket
   lifetime.

10.7.1.  Issuing a Ticket

   After a PQ query that carries a ciphertext has been decrypted, both
   parties hold <shared-key>.  They derive a resumption secret:

   resume-secret ::= HKDF-SHA256(IKM  = <shared-key>,
                                 salt = <client-magic> <client-nonce>,
                                 info = "DNSCrypt-PQ-resume-secret-v1",
                                 L    = 32)

Denis                   Expires 22 December 2026               [Page 31]
Internet-Draft                  DNSCrypt                       June 2026

   The resolver seals the resumption secret and the metadata needed to
   validate it later under TK:

   ticket-plain ::= resume-secret <es-version> <client-magic>
                    <serial> <ts-end> <ticket-expiry>
                    <profile-extension-hash>

   ticket ::= <ticket-key-id> <ticket-nonce>
              AE(TK, <ticket-nonce>, ticket-plain)

   The ticket is opaque, resolver-private state: its internal format and
   AEAD are an implementation choice and never need to interoperate
   between resolvers.  This document's reference construction reuses the
   XChaCha20_DJB-Poly1305 AEAD of Appendix 1 with no associated data;
   <ticket-key-id> selects the ticket key, so its integrity follows from
   decryption failing under the wrong key, and a dedicated TK keeps
   ticket sealing separate from query traffic.  The client stores the
   ticket together with the resume-secret it derived and the ticket
   expiry.  The ticket is delivered in the response control block:

   <control> ::= "PQDR" <control-version> <ticket-lifetime>
                 <ticket-len> <ticket>

   A resolver SHOULD issue a ticket in the first PQ response and MAY
   renew it on later responses; a client SHOULD adopt the most recent
   valid ticket it receives.  A captured ticket is of no use on its own:
   deriving the per-query key requires either the resume-secret, which
   only the client holds, or TK, which only the resolver holds.

10.7.2.  Resuming

   A resumed query uses a distinct packet form, with the resume magic in
   place of <client-magic> and the ticket in place of <client-pk>:

   <pq-resume-query> ::= <resume-magic> <ticket-len> <ticket>
                          <client-nonce> <encrypted-query>

   <resume-magic> is a reserved 8-byte value that MUST NOT collide with
   any valid <client-magic>, with <resolver-magic>, with the Anonymized
   DNSCrypt <anon-magic>, or with seven leading zero bytes.  On
   receiving a resumed query, the resolver locates the ticket key from
   <ticket-key-id>, opens the ticket, and rejects the query if the
   ticket cannot be opened, is expired, or if any sealed certificate-
   context field does not match an acceptable current certificate.  The
   sealed certificate-context fields are <es-version>, <client-magic>,
   <serial>, <ts-end>, and <profile-extension-hash>; <profile-extension-
   hash> is compared to SHA-256(<extensions>) for the matched
   certificate.  It then derives the per-query key:

Denis                   Expires 22 December 2026               [Page 32]
Internet-Draft                  DNSCrypt                       June 2026

   <shared-key> ::= HKDF-SHA256(IKM  = resume-secret,
                                salt = <client-magic> <client-nonce>,
                                info = "DNSCrypt-PQ-resumed-query-v1"
                                       SHA-256(<ticket>),
                                L    = 32)

   The client performs the same derivation.  The nonce construction is
   unchanged.  Each resumed query thus uses a fresh key derived from its
   own <client-nonce>; clients MUST still use a distinct <client-nonce>
   for each resumed query, because repeating it would repeat both the
   key and the AEAD nonce.  If a ticket cannot be opened or validated,
   the resolver MUST silently drop the query, with no response
   distinguishable from any other dropped query.  A client that receives
   no response retries with a query that carries a ciphertext, obtaining
   a fresh ticket.

10.7.3.  Resumption Considerations

   A ticket bounds forward secrecy for resumed traffic: compromise of TK
   exposes only the resumed queries whose tickets are still valid under
   that ticket-key epoch, and never the queries that carried a
   ciphertext.  The ticket lifetime SHOULD be short and MUST NOT exceed
   the resolver certificate lifetime or the ticket-key lifetime.  A
   ticket is also a linkable handle for its lifetime; a client that
   prioritizes unlinkability, in particular over Anonymized DNSCrypt,
   SHOULD send queries that carry a ciphertext rather than resuming.
   Resumed queries are replayable to the same extent as any stateless
   DNSCrypt query, and clients discard stale or duplicate responses with
   outstanding-query nonce tracking as usual.

   A resolver implementing PQ SHOULD support ticket issuance and
   resumption, since high query volumes make per-query decapsulation
   costly.  A resolver SHOULD rate-limit decapsulation of queries that
   carry a ciphertext and prioritize valid resumed traffic under load.

10.8.  PQ and Anonymized DNSCrypt

   Anonymized DNSCrypt relays forward opaque DNSCrypt queries and
   require no changes for PQ.  A query that carries a ciphertext keeps
   the classical query shape with a larger <client-pk> field, and a
   resumed query uses the resume shape above; both are opaque to a
   relay.  The relay check that an encrypted response is smaller than
   the query is satisfied automatically for queries that carry a
   ciphertext, because they are large.  A resumed query is small, so a
   client using Anonymized DNSCrypt MUST keep enough padding on a
   resumed UDP query for the relay's encrypted-response size check to
   pass, or send it over TCP.

Denis                   Expires 22 December 2026               [Page 33]
Internet-Draft                  DNSCrypt                       June 2026

   Certificate retrieval through a relay is different: the relay
   forwards the certificate query to the resolver over UDP and forwards
   a matching certificate response back to the client, and it applies
   the same response-size check, dropping any response larger than the
   query it relayed.  A client that wants a PQ certificate over UDP
   through a relay therefore pads its certificate query to at least the
   size of the expected certificate response, as described in
   Certificate Retrieval Amplification, so the response passes both the
   resolver's and the relay's anti-amplification check.  A client that
   does not, or cannot, pad far enough receives the classical
   certificate with the TC flag set and retries over TCP.

10.9.  PQ Downgrade Protection

   An on-path attacker cannot forge a certificate, but it can drop the
   PQ records from an unauthenticated certificate response, leaving only
   a classical certificate and pushing the client onto a quantum-
   vulnerable exchange.  A signature cannot prevent deletion.  A client
   that has been provisioned with the knowledge that a resolver supports
   PQ, for example through a flag in the DNS stamp that already carries
   the provider name and public key, MUST NOT fall back to a classical
   <es-version> for that resolver.  A client without such provisioning
   MAY fall back, accepting the classical risk.

10.10.  PQ Security Considerations

   Confidentiality against a future quantum adversary holds as long as
   either ML-KEM-768 or X25519 is unbroken, because X-Wing is a hybrid.

   Resolver authentication is as in classical DNSCrypt: the client
   encapsulates to a signed encapsulation key, and only the holder of
   the corresponding secret key can recover the shared secret and
   produce an authenticated response.

   The KEM ciphertext is not separately authenticated and does not need
   to be.  X-Wing, through ML-KEM-768, uses implicit rejection: a
   malformed but correctly sized ciphertext yields a pseudo-random
   shared secret rather than an error, so a tampered ciphertext produces
   a different key and authentication fails.  A resolver MUST drop such
   a query exactly as it drops any query that fails authentication, and
   the outcome on the wire MUST NOT reveal, through a distinct response,
   error, or timing, whether decapsulation, key derivation, or
   authentication failed; otherwise the resolver becomes a decapsulation
   oracle.  KEM and X25519 operations SHOULD be constant-time with
   respect to secret data.

Denis                   Expires 22 December 2026               [Page 34]
Internet-Draft                  DNSCrypt                       June 2026

   Forward secrecy for queries that carry a ciphertext has the same
   granularity as classical resolver-key rotation, since the resolver
   KEM key is short-term and the client's encapsulation randomness is
   fresh per query.  Resumed queries inherit the shorter of the ticket
   lifetime and the ticket-key lifetime.

   Nonce uniqueness is required as in classical DNSCrypt.  For queries
   that carry a ciphertext, each has an independent key, so cross-query
   collisions cannot occur; for resumed queries the per-query key
   derivation uses <client-nonce>, so clients MUST keep it unique for
   the lifetime of a ticket.

11.  IANA Considerations

   This document has no IANA actions.

12.  Appendix 1: The Box-XChaChaPoly Algorithm

   The Box-XChaChaPoly algorithm combines the X25519 [RFC7748] key
   exchange mechanism with a variant of the ChaCha20-Poly1305
   construction specified in [RFC8439].

12.1.  Conventions and Definitions

   *  x[a..]: the subarray of x starting at index a, and extending to
      the last index of x

   *  x[a..b]: the subarray of x starting at index a and ending at index
      b.

   *  LOAD32_LE(p): returns a 32-bit unsigned integer from the 4-byte
      array p

   *  STORE32_LE(p, x): stores the 32-bit unsigned integer x into the
      4-byte array p

12.2.  HChaCha20

   HChaCha20 is based on the construction and security proof used to
   create XSalsa20, an extended-nonce variant of Salsa20.

   The HChaCha20 function takes the following input parameters:

   *  <k>: secret key

   *  <in>: a 128-bit input

   and returns a 256-bit keyed hash.

Denis                   Expires 22 December 2026               [Page 35]
Internet-Draft                  DNSCrypt                       June 2026

   The function can be implemented using an existing IETF-compliant
   ChaCha20 implementation as follows:

   block_bytes = ChaCha20(msg={0}**64, nonce=in[4..16],
                          counter=LOAD32_LE(in[0..4]), key=k)

   block_out[0] = LOAD32_LE(block_bytes[ 0..][0..4]) - 0x61707865
   block_out[1] = LOAD32_LE(block_bytes[ 4..][0..4]) - 0x3320646e
   block_out[2] = LOAD32_LE(block_bytes[ 8..][0..4]) - 0x79622d32
   block_out[3] = LOAD32_LE(block_bytes[12..][0..4]) - 0x6b206574
   block_out[4] =
      LOAD32_LE(block_bytes[48..][0..4]) - LOAD32_LE(in[ 0..][0..4])
   block_out[5] =
      LOAD32_LE(block_bytes[52..][0..4]) - LOAD32_LE(in[ 4..][0..4])
   block_out[6] =
      LOAD32_LE(block_bytes[56..][0..4]) - LOAD32_LE(in[ 8..][0..4])
   block_out[7] =
      LOAD32_LE(block_bytes[60..][0..4]) - LOAD32_LE(in[12..][0..4])

   for i in 0..8:
       STORE32_LE(out[i * 4..][0..4], block_out[i])

   return out

12.3.  Test Vector For The HChaCha20 Block Function

   k:    000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f

   in:   000102030405060708090a0b0c0d0e0f

   out:  51e3ff45a895675c4b33b46c64f4a9ace110d34df6a2ceab486372bacbd3eff6

12.4.  ChaCha20_DJB

   As opposed to the version standardized for IETF protocols, ChaCha20
   was originally designed to have a 8 byte nonce.

   For the needs of TLS, [RFC8439] changed this by setting N_MIN and
   N_MAX to 12, at the expense of a smaller internal counter.

   DNSCrypt uses ChaCha20 as originally specified, with N_MIN = N_MAX =
   8.  We refer to this variant as ChaCha20_DJB.

   The internal counter in ChaCha20_DJB is 4 bytes larger than ChaCha20.
   There are no other differences between ChaCha20_DJB and ChaCha20.

Denis                   Expires 22 December 2026               [Page 36]
Internet-Draft                  DNSCrypt                       June 2026

12.5.  XChaCha20_DJB

   XChaCha20_DJB can be constructed from an existing ChaCha20
   implementation and the HChaCha20 function.

   All that needs to be done is:

   1.  Pass the key and the first 16 bytes of the 24-byte nonce to
       HChaCha20 to obtain the subkey.

   2.  Use the subkey and remaining 8 byte nonce with ChaCha20_DJB.

12.6.  XChaCha20_DJB-Poly1305

   XChaCha20 is a stream cipher and offers no integrity guarantees
   without being combined with a MAC algorithm (e.g. Poly1305).

   XChaCha20_DJB-Poly1305 adds an authentication tag to the ciphertext
   encrypted with XChaCha20_DJB.  It is the combined mode used by the
   NaCl secretbox and crypto_box constructions, instantiated with
   XChaCha20_DJB.  The one-time Poly1305 key is taken from the start of
   the keystream, and the message is encrypted with the keystream that
   immediately follows it.  No separate keystream block is reserved for
   the Poly1305 key, so this layout differs from the AEAD of [RFC8439],
   where the message starts at block counter 1 and the rest of the first
   keystream block is discarded.

   Concretely, for a key <k> and a message <m>:

   *  <k>: encryption key

   *  <m>: message to encrypt

   *  <keystream>: the XChaCha20_DJB keystream produced from <k> and the
      24-byte nonce, starting at block counter 0.

   *  <poly-key>: <keystream>[0..32], the one-time Poly1305 key.  These
      bytes are not transmitted.

   *  <ct>: <m> XOR <keystream>[32..32 + length(<m>)], the ciphertext.

   *  <tag>: Poly1305(<poly-key>, <ct>), the 16-byte tag.

   *  XChaCha20_DJB-Poly1305(<k>, <m>): <tag> || <ct>

Denis                   Expires 22 December 2026               [Page 37]
Internet-Draft                  DNSCrypt                       June 2026

   Equivalently, XChaCha20_DJB is run over the buffer <zero32> || <m>,
   where <zero32> is 32 NUL bytes, starting at block counter 0; the
   first 32 output bytes are taken as <poly-key>, and the remaining
   length(<m>) bytes are <ct>.

12.7.  The Box-XChaChaPoly Algorithm

   The Box-XChaChaPoly algorithm combines the key exchange mechanism
   X25519 defined [RFC7748] with the XChaCha20_DJB-Poly1305
   authenticated encryption algorithm.

   *  <k>: encryption key

   *  <m>: message to encrypt

   *  <pk>: recipient's public key

   *  <sk>: sender's secret key

   *  <zero16>: 16 NUL bytes

   *  <sk'>: HChaCha20(X25519(<pk>, <sk>), <zero16>), the shared key

   *  Box-XChaChaPoly(pk, sk, m): XChaCha20_DJB-Poly1305(<sk'>, <m>)

13.  Appendix 2: DNSCrypt Test Vectors

   This appendix provides complete, reproducible test vectors for the
   regular DNSCrypt protocol with the Box-XChaChaPoly encryption system
   of Appendix 1, that is, <es-version> 0x00 0x02: X25519 key exchange,
   the XChaCha20_DJB-Poly1305 AEAD, and Ed25519 certificate signatures.

   All randomness is pinned so the vectors are reproducible.  Every
   value is given in full as a hexadecimal string, wrapped to 32 bytes
   per line.  The field and byte order of each artifact matches the
   Protocol Components and Certificates sections.  These vectors were
   cross-checked byte for byte against the dnscrypt-proxy reference
   implementation.

13.1.  Pinned Inputs

   The secret keys below are the raw 32-byte X25519 scalars as stored by
   an implementation; X25519 clamps them internally.  The provider
   signing key is given as its 32-byte Ed25519 seed.

Denis                   Expires 22 December 2026               [Page 38]
Internet-Draft                  DNSCrypt                       June 2026

   +===============================+========+=========================+
   | Input                         | Length | Value                   |
   +===============================+========+=========================+
   | provider Ed25519 signing seed | 32     | 00 01 02 ... 1f         |
   +-------------------------------+--------+-------------------------+
   | resolver X25519 secret key    | 32     | 20 21 22 ... 3f         |
   +-------------------------------+--------+-------------------------+
   | client X25519 secret key      | 32     | 40 41 42 ... 5f         |
   +-------------------------------+--------+-------------------------+
   | <client-magic>                | 8      | b1 b2 b3 b4 b5 b6 b7 b8 |
   +-------------------------------+--------+-------------------------+
   | <es-version>                  | 2      | 00 02                   |
   +-------------------------------+--------+-------------------------+
   | <protocol-minor-version>      | 2      | 00 00                   |
   +-------------------------------+--------+-------------------------+
   | <serial>                      | 4      | 00 00 00 01             |
   +-------------------------------+--------+-------------------------+
   | <ts-start>                    | 4      | 68 00 00 00             |
   +-------------------------------+--------+-------------------------+
   | <ts-end>                      | 4      | 68 01 51 80             |
   +-------------------------------+--------+-------------------------+
   | query <client-nonce>          | 12     | a0 a1 a2 ... ab         |
   +-------------------------------+--------+-------------------------+
   | response <resolver-nonce>     | 12     | c0 c1 c2 ... cb         |
   +-------------------------------+--------+-------------------------+
   | <extensions>                  | 0      | empty                   |
   +-------------------------------+--------+-------------------------+

                                 Table 1

   <client-magic> is chosen by the resolver and carried in the
   certificate; the client copies it verbatim into the first 8 bytes of
   every query. <ts-start> and <ts-end> span exactly 86400 seconds (a
   one-day validity window).  The fixed protocol constants are <cert-
   magic> = 44 4e 53 43, <es-version> = 00 02, and <resolver-magic> = 72
   36 66 6e 76 57 6a 38.

   The example DNS messages are a query and its answer for
   www.example.com:

   dns-query (33 bytes), id 0x1234, RD set, A? www.example.com IN:
     12340100000100000000000003777777076578616d706c6503636f6d00000100
     01
   dns-response (49 bytes), A 93.184.216.34, TTL 3600:
     12348180000100010000000003777777076578616d706c6503636f6d00000100
     01c00c0001000100000e1000045db8d822

Denis                   Expires 22 December 2026               [Page 39]
Internet-Draft                  DNSCrypt                       June 2026

13.2.  Public Keys and Shared Key

   provider-ed25519-pk = Ed25519 public key for the signing seed:
     03a107bff3ce10be1d70dd18e74bc09967e4d6309ba50d5f1ddc8664125531b8
   resolver-pk = X25519 base-point mult of the resolver secret key:
     358072d6365880d1aeea329adf9121383851ed21a28e3b75e965d0d2cd166254
   client-pk = X25519 base-point mult of the client secret key:
     79a631eede1bf9c98f12032cdeadd0e7a079398fc786b88cc846ec89af85a51a

   x25519-shared-point = X25519(client-sk, resolver-pk)
                       = X25519(resolver-sk, client-pk):
     04c304fb1ca83cee75e206344231f33797e07d9929db670994b7c6fbeb1dc255
   shared-key = HChaCha20(key = x25519-shared-point, in = 16 NUL bytes):
     335d32f2d65e6623cbbd05b6539c9575fee16cb5405fe839ab4bd291fdf13262

   The same shared-key is computed by the client from (client-sk,
   resolver-pk) and by the resolver from (resolver-sk, client-pk).

13.3.  Certificate

   The signature covers <resolver-pk> <client-magic> <serial> <ts-start>
   <ts-end> <extensions>, with <extensions> empty in this protocol
   version:

signed input (52 bytes):
  358072d6365880d1aeea329adf9121383851ed21a28e3b75e965d0d2cd166254
  b1b2b3b4b5b6b7b8000000016800000068015180
signature = Ed25519.Sign(provider signing seed, signed input) (64 bytes):
  3a570ea17f47b80217977fbb455840bfd50ab32f5fbf2aabc173a6a49b7a49ca
  55362a6c5dec47657cf515e9f99382a316dfecd964b94d1c4659cac45961400c

   The full certificate, as carried in a TXT record, is 124 bytes:

Denis                   Expires 22 December 2026               [Page 40]
Internet-Draft                  DNSCrypt                       June 2026

     +========+==========================+========+=================+
     | Offset | Field                    | Length | Value           |
     +========+==========================+========+=================+
     | 0      | <cert-magic>             | 4      | 44 4e 53 43     |
     +--------+--------------------------+--------+-----------------+
     | 4      | <es-version>             | 2      | 00 02           |
     +--------+--------------------------+--------+-----------------+
     | 6      | <protocol-minor-version> | 2      | 00 00           |
     +--------+--------------------------+--------+-----------------+
     | 8      | <signature>              | 64     | 3a 57 ... 40 0c |
     +--------+--------------------------+--------+-----------------+
     | 72     | <resolver-pk>            | 32     | 35 80 ... 62 54 |
     +--------+--------------------------+--------+-----------------+
     | 104    | <client-magic>           | 8      | b1 b2 b3 b4 b5  |
     |        |                          |        | b6 b7 b8        |
     +--------+--------------------------+--------+-----------------+
     | 112    | <serial>                 | 4      | 00 00 00 01     |
     +--------+--------------------------+--------+-----------------+
     | 116    | <ts-start>               | 4      | 68 00 00 00     |
     +--------+--------------------------+--------+-----------------+
     | 120    | <ts-end>                 | 4      | 68 01 51 80     |
     +--------+--------------------------+--------+-----------------+

                                 Table 2

   certificate (124 bytes):
     444e5343000200003a570ea17f47b80217977fbb455840bfd50ab32f5fbf2aab
     c173a6a49b7a49ca55362a6c5dec47657cf515e9f99382a316dfecd964b94d1c
     4659cac45961400c358072d6365880d1aeea329adf9121383851ed21a28e3b75
     e965d0d2cd166254b1b2b3b4b5b6b7b8000000016800000068015180

13.4.  Client Query (UDP)

   The plaintext is the DNS query padded with ISO/IEC 7816-4 to <min-
   query-len> = 256 bytes: one 0x80 byte followed by NUL bytes.  The
   24-byte AEAD nonce is the 12-byte client nonce followed by 12 NUL
   bytes.

Denis                   Expires 22 December 2026               [Page 41]
Internet-Draft                  DNSCrypt                       June 2026

   padded query plaintext (256 bytes):
     12340100000100000000000003777777076578616d706c6503636f6d00000100
     0180000000000000000000000000000000000000000000000000000000000000
     0000000000000000000000000000000000000000000000000000000000000000
     0000000000000000000000000000000000000000000000000000000000000000
     0000000000000000000000000000000000000000000000000000000000000000
     0000000000000000000000000000000000000000000000000000000000000000
     0000000000000000000000000000000000000000000000000000000000000000
     0000000000000000000000000000000000000000000000000000000000000000
   query AEAD nonce (24 bytes):
     a0a1a2a3a4a5a6a7a8a9aaab000000000000000000000000
   encrypted-query = tag (16) || ciphertext (256) (272 bytes):
     2dae527c26386d5cd4e61152db6dd1812ff6aaf7644fc122afc70b1b580b18f1
     0fbc26577abc759152cde31cd0afc5c5f452f8654815469723300819bed5a120
     15c044b94d63ec1f79e48a23968e437feb8bb8720cf4e60a0499746190c8b3eb
     83aeb0d858df77794270b861f86644502be0d22d6f0b2b132e9ca68538300c8d
     68b8e3c48190cbbf96d602f38dfc3b4d642016ceeaf4bc2c2ded9483b9f9d4ee
     d703a0bebc252add8822d4b9152e30670bcde9ea75a0e3e67ea576e9b1262bb2
     b25b4f9432311b75a2238b34bf4f868da182b85dccb1762a703bba31d04d77b4
     c57ec9039663959793677588b3a74ae409b0f16374dd64cbd6d47d801725b014
     ce9ddaf6f1aa30688c8efcbfde1d5d1d

   Query on the wire (324 bytes):

     +========+===================+========+=========================+
     | Offset | Field             | Length | Value                   |
     +========+===================+========+=========================+
     | 0      | <client-magic>    | 8      | b1 b2 b3 b4 b5 b6 b7 b8 |
     +--------+-------------------+--------+-------------------------+
     | 8      | <client-pk>       | 32     | 79 a6 ... a5 1a         |
     +--------+-------------------+--------+-------------------------+
     | 40     | <client-nonce>    | 12     | a0 a1 ... ab            |
     +--------+-------------------+--------+-------------------------+
     | 52     | <encrypted-query> | 272    | 2d ae ... d5 1d         |
     +--------+-------------------+--------+-------------------------+

                                  Table 3

Denis                   Expires 22 December 2026               [Page 42]
Internet-Draft                  DNSCrypt                       June 2026

   full query wire (324 bytes):
     b1b2b3b4b5b6b7b879a631eede1bf9c98f12032cdeadd0e7a079398fc786b88c
     c846ec89af85a51aa0a1a2a3a4a5a6a7a8a9aaab2dae527c26386d5cd4e61152
     db6dd1812ff6aaf7644fc122afc70b1b580b18f10fbc26577abc759152cde31c
     d0afc5c5f452f8654815469723300819bed5a12015c044b94d63ec1f79e48a23
     968e437feb8bb8720cf4e60a0499746190c8b3eb83aeb0d858df77794270b861
     f86644502be0d22d6f0b2b132e9ca68538300c8d68b8e3c48190cbbf96d602f3
     8dfc3b4d642016ceeaf4bc2c2ded9483b9f9d4eed703a0bebc252add8822d4b9
     152e30670bcde9ea75a0e3e67ea576e9b1262bb2b25b4f9432311b75a2238b34
     bf4f868da182b85dccb1762a703bba31d04d77b4c57ec9039663959793677588
     b3a74ae409b0f16374dd64cbd6d47d801725b014ce9ddaf6f1aa30688c8efcbf
     de1d5d1d

13.5.  Server Response (UDP)

   The plaintext is the DNS response padded with ISO/IEC 7816-4 to 64
   bytes.  The 24-byte AEAD nonce is the client nonce followed by the
   resolver nonce.

   padded response plaintext (64 bytes):
     12348180000100010000000003777777076578616d706c6503636f6d00000100
     01c00c0001000100000e1000045db8d822800000000000000000000000000000
   response AEAD nonce (24 bytes):
     a0a1a2a3a4a5a6a7a8a9aaabc0c1c2c3c4c5c6c7c8c9cacb
   encrypted-response = tag (16) || ciphertext (64) (80 bytes):
     f2670995c6d37c2f8d2016029dd5970b893de83c02815ece9b48d9fd0b0dca87
     41674142fbd8e12c1120b111f366326aa71c89823a2931ac5c860dad49685ed6
     cc22cc13e829d2e51d1c00ea64d1d39d

   Response on the wire (112 bytes):

   +========+======================+========+=========================+
   | Offset | Field                | Length | Value                   |
   +========+======================+========+=========================+
   | 0      | <resolver-magic>     | 8      | 72 36 66 6e 76 57 6a 38 |
   +--------+----------------------+--------+-------------------------+
   | 8      | <nonce>              | 24     | a0..ab (client) c0..cb  |
   |        |                      |        | (resolver)              |
   +--------+----------------------+--------+-------------------------+
   | 32     | <encrypted-response> | 80     | f2 67 ... d3 9d         |
   +--------+----------------------+--------+-------------------------+

                                 Table 4

Denis                   Expires 22 December 2026               [Page 43]
Internet-Draft                  DNSCrypt                       June 2026

   full response wire (112 bytes):
     7236666e76576a38a0a1a2a3a4a5a6a7a8a9aaabc0c1c2c3c4c5c6c7c8c9cacb
     f2670995c6d37c2f8d2016029dd5970b893de83c02815ece9b48d9fd0b0dca87
     41674142fbd8e12c1120b111f366326aa71c89823a2931ac5c860dad49685ed6
     cc22cc13e829d2e51d1c00ea64d1d39d

13.6.  Padding and Transport Notes

   The vectors fix the padded plaintext lengths so they are
   reproducible:

   *  The query plaintext is padded to <min-query-len> = 256 bytes, the
      minimum permitted length for a client query inclusive of padding
      and a multiple of 64.  An implementation MAY use a larger <min-
      query-len> that is also a multiple of 64; dnscrypt-proxy defaults
      to 512.

   *  The response plaintext is padded to the smallest multiple of 64
      that holds the response plus at least one padding byte, here 64
      bytes.  The exact response padding length is otherwise an
      implementation choice, subject to the response being no larger
      than the query over UDP.

   *  Over TCP the encryption is identical, but each packet is prefixed
      with a two-byte big-endian length, and the query padding length is
      chosen at random as described in Query Processing.

13.7.  Negative Cases

   These vectors pin the required failure behavior.  None of them
   produce a distinguishable on-the-wire signal beyond "no response" or
   "certificate rejected".

   1.  Tampered ciphertext: flipping any byte of <encrypted-query> or
       <encrypted-response> makes Poly1305 verification fail.  The
       receiver MUST drop the packet.

   2.  Bad padding: after a successful decryption, the plaintext MUST
       end with a 0x80 byte followed by zero or more NUL bytes.  A
       plaintext that does not MUST be rejected.

   3.  Wrong <client-magic>: a query whose first 8 bytes do not match
       the <client-magic> of any certificate the resolver currently
       serves is not a DNSCrypt query for this resolver.  The resolver
       MUST NOT treat it as one.

Denis                   Expires 22 December 2026               [Page 44]
Internet-Draft                  DNSCrypt                       June 2026

   4.  Response nonce mismatch: the client MUST verify that the <client-
       nonce> prefix (first 12 bytes) of the response nonce matches an
       outstanding query, and otherwise drop the response.

   5.  Weak public key: if the X25519 shared point is the all-zero
       value, the public key is of low order and the shared key MUST be
       rejected.

   6.  Certificate outside its validity window: a certificate whose <ts-
       start>/<ts-end> does not include the current time MUST NOT be
       used, and among valid certificates the client picks the highest
       <serial>.

14.  Appendix 3: PQ Test Vector Structure

   This appendix fixes the exact field order and byte order for PQ.
   Long cryptographic outputs, namely KEM keys and ciphertexts, the
   Ed25519 signature, AEAD outputs, and HKDF outputs, are left as
   placeholders to be filled in by a reference implementation.
   Everything structural is pinned here, so that two implementations
   cannot disagree about layout even before the hex values exist.

   All integers are big-endian.  Every AEAD operation is XChaCha20_DJB-
   Poly1305 as defined in Appendix 1, with the 16-byte tag prepended to
   the ciphertext.  Every key derivation is HKDF-SHA256 [RFC5869].
   Placeholders are written [name: N bytes]; the concrete values they
   stand for appear in the Generated Values subsection at the end of
   this appendix.

14.1.  Provisional Assignments

   The following values are the provisional assignments used by these
   vectors.  They are normative for the vectors but expected to be
   confirmed before publication.

Denis                   Expires 22 December 2026               [Page 45]
Internet-Draft                  DNSCrypt                       June 2026

   +==========================+=======================================+
   | Item                     | Value                                 |
   +==========================+=======================================+
   | <es-version> (X-Wing)    | 0x00 0x03                             |
   +--------------------------+---------------------------------------+
   | <resume-magic>           | 50 51 52 65 73 75 6d 65 ("PQResume")  |
   +--------------------------+---------------------------------------+
   | <kdf-id>                 | 0x01 (HKDF-SHA256)                    |
   +--------------------------+---------------------------------------+
   | <aead-id>                | 0x01 (XChaCha20_DJB-Poly1305)         |
   +--------------------------+---------------------------------------+
   | ticket AEAD              | XChaCha20_DJB-Poly1305, 24-byte nonce |
   +--------------------------+---------------------------------------+
   | <ticket-key-id>          | 4 bytes                               |
   +--------------------------+---------------------------------------+
   | <ticket-nonce>           | 24 bytes                              |
   +--------------------------+---------------------------------------+
   | <ticket-expiry>          | 4-byte Unix timestamp                 |
   +--------------------------+---------------------------------------+
   | <ticket-lifetime>        | 4-byte seconds                        |
   +--------------------------+---------------------------------------+
   | <profile-extension-hash> | SHA-256(<extensions>), 32 bytes       |
   +--------------------------+---------------------------------------+

                                 Table 5

14.2.  Pinned Inputs

   All randomness is fixed so the vectors are reproducible.  Short
   values are given concretely; values produced by a primitive are
   placeholders.

   +===============================+========+=========================+
   | Input                         | Length | Value                   |
   +===============================+========+=========================+
   | provider Ed25519 signing seed | 32     | 00 01 02 ... 1f         |
   +-------------------------------+--------+-------------------------+
   | provider Ed25519 public key   | 32     | [provider-pk: 32 bytes] |
   +-------------------------------+--------+-------------------------+
   | resolver X-Wing secret seed   | 32     | 20 21 22 ... 3f         |
   +-------------------------------+--------+-------------------------+
   | resolver X-Wing public key    | 1216   | [resolver-pk: 1216      |
   |                               |        | bytes]                  |
   +-------------------------------+--------+-------------------------+
   | client X-Wing encapsulation   | 64     | 40 41 42 ... 7f         |
   | seed                          |        |                         |
   +-------------------------------+--------+-------------------------+
   | <es-version>                  | 2      | 00 03                   |

Denis                   Expires 22 December 2026               [Page 46]
Internet-Draft                  DNSCrypt                       June 2026

   +-------------------------------+--------+-------------------------+
   | <protocol-minor-version>      | 2      | 00 00                   |
   +-------------------------------+--------+-------------------------+
   | <client-magic>                | 8      | a1 b2 c3 d4 e5 f6 07 18 |
   +-------------------------------+--------+-------------------------+
   | <serial>                      | 4      | 00 00 00 01             |
   +-------------------------------+--------+-------------------------+
   | <ts-start>                    | 4      | 68 00 00 00             |
   +-------------------------------+--------+-------------------------+
   | <ts-end>                      | 4      | 68 01 51 80             |
   +-------------------------------+--------+-------------------------+
   | query <client-nonce>          | 12     | b0 b1 b2 ... bb         |
   +-------------------------------+--------+-------------------------+
   | response <resolver-nonce>     | 12     | c0 c1 c2 ... cb         |
   +-------------------------------+--------+-------------------------+
   | ticket key TK                 | 32     | 80 81 82 ... 9f         |
   +-------------------------------+--------+-------------------------+
   | <ticket-key-id>               | 4      | 00 00 00 01             |
   +-------------------------------+--------+-------------------------+
   | <ticket-nonce>                | 24     | d0 d1 d2 ... e7         |
   +-------------------------------+--------+-------------------------+
   | <ticket-expiry>               | 4      | 68 00 02 58             |
   +-------------------------------+--------+-------------------------+
   | <ticket-lifetime>             | 4      | 00 00 01 2c             |
   +-------------------------------+--------+-------------------------+
   | resumed <client-nonce>        | 12     | f0 f1 f2 ... fb         |
   +-------------------------------+--------+-------------------------+
   | resumed <resolver-nonce>      | 12     | 10 11 12 ... 1b         |
   +-------------------------------+--------+-------------------------+
   | example DNS query (A?         | 33     | [dns-query: 33 bytes]   |
   | www.example.com)              |        |                         |
   +-------------------------------+--------+-------------------------+
   | example DNS response          | var    | [dns-response]          |
   +-------------------------------+--------+-------------------------+

                                 Table 6

14.3.  Profile Extension and Signature Input

   The PQ profile extension is the entire <extensions> field in this
   revision:

Denis                   Expires 22 December 2026               [Page 47]
Internet-Draft                  DNSCrypt                       June 2026

   pq-profile-ext =
       "PQD"             50 51 44
       ext-version       01
       es-version        00 03
       kdf-id            01            (HKDF-SHA256)
       aead-id           01            (XChaCha20_DJB-Poly1305)
       resolver-pk-len   04 c0         (1216)
       client-kex-len    04 60         (1120)
                                       -> 12 bytes total

   The Ed25519 signature is computed over the existing field set,
   unchanged:

sig-input = resolver-pk (1216) || client-magic (8) || serial (4)
            || ts-start (4) || ts-end (4) || extensions (12)
          -> 1248 bytes
signature = Ed25519.Sign(provider-seed, sig-input)  = [signature: 64 bytes]

   The full certificate (92 fixed bytes + 1216-byte resolver key +
   12-byte extensions = 1320 bytes):

Denis                   Expires 22 December 2026               [Page 48]
Internet-Draft                  DNSCrypt                       June 2026

    +========+==========================+========+===================+
    | Offset | Field                    | Length | Value             |
    +========+==========================+========+===================+
    | 0      | <cert-magic>             | 4      | 44 4e 53 43       |
    +--------+--------------------------+--------+-------------------+
    | 4      | <es-version>             | 2      | 00 03             |
    +--------+--------------------------+--------+-------------------+
    | 6      | <protocol-minor-version> | 2      | 00 00             |
    +--------+--------------------------+--------+-------------------+
    | 8      | <signature>              | 64     | [signature: 64    |
    |        |                          |        | bytes]            |
    +--------+--------------------------+--------+-------------------+
    | 72     | <resolver-pk>            | 1216   | [resolver-pk:     |
    |        |                          |        | 1216 bytes]       |
    +--------+--------------------------+--------+-------------------+
    | 1288   | <client-magic>           | 8      | a1 b2 c3 d4 e5 f6 |
    |        |                          |        | 07 18             |
    +--------+--------------------------+--------+-------------------+
    | 1296   | <serial>                 | 4      | 00 00 00 01       |
    +--------+--------------------------+--------+-------------------+
    | 1300   | <ts-start>               | 4      | 68 00 00 00       |
    +--------+--------------------------+--------+-------------------+
    | 1304   | <ts-end>                 | 4      | 68 01 51 80       |
    +--------+--------------------------+--------+-------------------+
    | 1308   | <extensions>             | 12     | 50 51 44 01 00 03 |
    |        |                          |        | 01 01 04 c0 04 60 |
    +--------+--------------------------+--------+-------------------+

                                 Table 7

14.4.  Full X-Wing Query

Denis                   Expires 22 December 2026               [Page 49]
Internet-Draft                  DNSCrypt                       June 2026

(resolver-pk, resolver-sk) = X-Wing.GenerateKeyPairDerand(resolver-seed)
(ct, kem-ss)               = X-Wing.EncapsulateDerand(resolver-pk, eseed)
    ct      = [ct: 1120 bytes]
    kem-ss  = [kem-ss: 32 bytes]

cert-context = "DNSCrypt-PQ-v1"            (14 bytes)
            || es-version (00 03) || protocol-minor-version (00 00)
            || resolver-pk (1216) || client-magic (8)
            || serial (4) || ts-start (4) || ts-end (4)
            || extensions (12)

shared-key = HKDF-SHA256(
                 IKM  = kem-ss,
                 salt = es-version || client-magic
                        (10 bytes: 00 03 a1 b2 c3 d4 e5 f6 07 18),
                 info = cert-context || ct,
                 L    = 32)                  = [shared-key: 32 bytes]

query-nonce = client-nonce || (12 * 00)      (24 bytes)
plaintext   = dns-query (33) || 80 || (30 * 00)   (padded to 64; ISO/IEC 7816-4)
encrypted-query = tag (16) || ciphertext (64)     = [enc-query: 80 bytes]

   Query on the wire (1220 bytes):

     +========+===================+========+=========================+
     | Offset | Field             | Length | Value                   |
     +========+===================+========+=========================+
     | 0      | <client-magic>    | 8      | a1 b2 c3 d4 e5 f6 07 18 |
     +--------+-------------------+--------+-------------------------+
     | 8      | <client-pk> = ct  | 1120   | [ct: 1120 bytes]        |
     +--------+-------------------+--------+-------------------------+
     | 1128   | <client-nonce>    | 12     | b0 b1 ... bb            |
     +--------+-------------------+--------+-------------------------+
     | 1140   | <encrypted-query> | 80     | [enc-query: 80 bytes]   |
     +--------+-------------------+--------+-------------------------+

                                  Table 8

14.5.  Full Response and Ticket Issuance

Denis                   Expires 22 December 2026               [Page 50]
Internet-Draft                  DNSCrypt                       June 2026

resume-secret = HKDF-SHA256(
                    IKM  = shared-key,
                    salt = client-magic || client-nonce  (20 bytes),
                    info = "DNSCrypt-PQ-resume-secret-v1",
                    L    = 32)               = [resume-secret: 32 bytes]

profile-extension-hash = SHA-256(extensions) = [peh: 32 bytes]

ticket-plain = resume-secret (32) || es-version (2) || client-magic (8)
            || serial (4) || ts-end (4) || ticket-expiry (4)
            || profile-extension-hash (32)   (86 bytes)

ticket = ticket-key-id (4) || ticket-nonce (24)
      || AE(TK, ticket-nonce, ticket-plain)
         where AE output = tag (16) || ciphertext (86)
      -> 4 + 24 + 102 = 130 bytes            = [ticket: 130 bytes]

control = "PQDR" (50 51 44 52) || control-version (01)
       || ticket-lifetime (00 00 01 2c) || ticket-len (00 82)
       || ticket (130)                       (141 bytes)

pq-response-plain = control-len (00 8d) || control (141)
                  || dns-response || pad-to-64

response-nonce = client-nonce || resolver-nonce   (24 bytes)
encrypted-response = tag (16) || ciphertext       = [enc-response]

   Response on the wire:

   +======================+========+===================================+
   | Field                | Length | Value                             |
   +======================+========+===================================+
   | <resolver-magic>     | 8      | 72 36 66 6e 76 57 6a 38           |
   +----------------------+--------+-----------------------------------+
   | <nonce>              | 24     | b0..bb (client) c0..cb            |
   |                      |        | (resolver)                        |
   +----------------------+--------+-----------------------------------+
   | <encrypted-response> | var    | [enc-response]                    |
   +----------------------+--------+-----------------------------------+

                                  Table 9

14.6.  Resumed Query and Response

Denis                   Expires 22 December 2026               [Page 51]
Internet-Draft                  DNSCrypt                       June 2026

resumed shared-key = HKDF-SHA256(
                         IKM  = resume-secret,
                         salt = client-magic || resumed-client-nonce (20 bytes),
                         info = "DNSCrypt-PQ-resumed-query-v1" || SHA-256(ticket),
                         L    = 32)          = [resumed-shared-key: 32 bytes]

query-nonce = resumed-client-nonce || (12 * 00)   (24 bytes)
plaintext   = dns-query || 80 || pad         (padded to 256: resumed floor)
encrypted-query = tag (16) || ciphertext (256)    = [enc-query: 272 bytes]

   Resumed query on the wire (424 bytes):

     +========+===================+========+=========================+
     | Offset | Field             | Length | Value                   |
     +========+===================+========+=========================+
     | 0      | <resume-magic>    | 8      | 50 51 52 65 73 75 6d 65 |
     +--------+-------------------+--------+-------------------------+
     | 8      | <ticket-len>      | 2      | 00 82 (130)             |
     +--------+-------------------+--------+-------------------------+
     | 10     | <ticket>          | 130    | [ticket: 130 bytes]     |
     +--------+-------------------+--------+-------------------------+
     | 140    | <client-nonce>    | 12     | f0 f1 ... fb            |
     +--------+-------------------+--------+-------------------------+
     | 152    | <encrypted-query> | 272    | [enc-query: 272 bytes]  |
     +--------+-------------------+--------+-------------------------+

                                  Table 10

   The resumed response reuses resumed shared-key with nonce resumed-
   client-nonce || resumed-resolver-nonce.  If the resolver issues no
   new ticket, the control block is empty (control-len = 00 00).

14.7.  Negative Cases

   These vectors pin the required failure behavior.  None of them
   produce a distinguishable on-the-wire signal beyond "no response" or
   "certificate rejected".

   1.  Bad profile-extension length: resolver-pk-len or client-kex-len
       in the extension disagrees with the actual field length, or pq-
       profile-ext is not 12 bytes.  The client MUST reject the
       certificate.

   2.  <es-version> mismatch: the on-the-wire <es-version> differs from
       the copy inside the signed extension.  The client MUST reject the
       certificate.

Denis                   Expires 22 December 2026               [Page 52]
Internet-Draft                  DNSCrypt                       June 2026

   3.  Corrupted ticket AEAD: one byte of the sealed region of <ticket>
       in a resumed query is flipped.  AEAD opening fails, and the
       resolver MUST silently drop the query.

   4.  Expired or rotated ticket: <ticket-expiry> is in the past, or
       <ticket-key-id> names a TK that has been rotated out.  The
       resolver MUST silently drop the query; the client re-handshakes
       with a query that carries a ciphertext.

   5.  Ticket context mismatch: any sealed certificate-context field in
       the ticket (<es-version>, <client-magic>, <serial>, <ts-end>, or
       <profile-extension-hash>) does not match the resumption context.
       The resolver MUST silently drop the query.

   6.  Malformed KEM ciphertext: one byte of <client-pk> in a query that
       carries a ciphertext is flipped.  X-Wing implicit rejection
       yields a different shared secret, authentication fails, and the
       resolver MUST silently drop the query, with no distinct error or
       timing.

   7.  Repeated nonce (client obligation): reusing a <client-nonce>
       under one ticket reuses both the derived key and the AEAD nonce;
       this is a client MUST NOT.  A stateless resolver cannot detect
       it, so there is no wire vector; it is listed to make the
       obligation explicit.

   8.  Under-padded resumed query: a resumed UDP query padded below the
       256-byte floor decrypts correctly but violates the anti-
       amplification rule, and an Anonymized DNSCrypt relay MAY drop it
       on the response-size check.

14.8.  Generated Values

   The values below were produced by a reference generator from the
   pinned inputs above, and are reproducible by any conformant
   implementation.  Values up to 141 bytes are given in full; longer
   artifacts are pinned by their SHA-256 digest.  The generator self-
   checks against the HChaCha20 known-answer test of Appendix 1 and the
   official X-Wing known-answer test, and verifies that X-Wing
   decapsulation recovers the encapsulated secret.

   provider-ed25519-pk (32):
     03a107bff3ce10be1d70dd18e74bc09967e4d6309ba50d5f1ddc8664125531b8
   resolver-pk (1216, SHA-256):
     a1f324bc0701f1234fbba7b11901023b3644f3bb8c6eb4ee4368d7e859eb6228
   client-kex / ct (1120, SHA-256):
     f6bf3f238e83f24cd444f2887e8fd32d630e07dbe6ca2f2b403aaf5333030c48
   kem-ss (32):

Denis                   Expires 22 December 2026               [Page 53]
Internet-Draft                  DNSCrypt                       June 2026

     8dac8602d4ce5e27e81335b54b25fdcaea86e56613214ee0522db4a5e0a38d50
   shared-key (32):
     e6d4ab9cffc9b49e2a64d80d7eb2dde280f806b89e834d596ad385b1dd75e9ef
   signature (64):
     811bab04e2e70c9d946296a93b4028d7c7bb84f32f597d3cf8aba29edc1b6b97
     4acc99dd00ec62cdcae477433d10bff20e1c432e1011ad8ad5324f68a294750c

   dns-query (33):
     12340100000100000000000003777777076578616d706c6503636f6d00000100
     01
   dns-response (49):
     12348180000100010000000003777777076578616d706c6503636f6d00000100
     01c00c0001000100000e1000045db8d822
   padded query plaintext (64):
     12340100000100000000000003777777076578616d706c6503636f6d00000100
     0180000000000000000000000000000000000000000000000000000000000000
   encrypted-query (80):
     c41764468cb42d3a837c51234c08be714af49e1a6830ea6da28178e9e280d76b
     ac1b87fd7f56515f2b2cc3d4715aaa42907c282db1edff0bc3b92cd535a710e2
     64859a5bdaf67c17ffa6e1c6f6e02a50
   full query wire (1220, SHA-256):
     65c3421776283f503779916e7b5c32d0d41c885508ad892b349688db6c901233

   resume-secret (32):
     df158804e3f8ddf383ff7c9d3128491b29437a894936ec72c68aed8a9553272b
   profile-extension-hash = SHA-256(extensions) (32):
     fab3bf4996c5d2fdfc330ec958d0a5b63624bf3fbdc0fedfa9d94b0941a4060c
   ticket-plain (86):
     df158804e3f8ddf383ff7c9d3128491b29437a894936ec72c68aed8a9553272b
     0003a1b2c3d4e5f60718000000016801518068000258fab3bf4996c5d2fdfc33
     0ec958d0a5b63624bf3fbdc0fedfa9d94b0941a4060c
   ticket (130):
     00000001d0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e1d90c86
     474574e0e51e82d8a29938896b0999e827138f8f452f21e044d9809f65a013cf
     ad8981be94c1354178b3e03dd518c28bcbaab962aa45246e446de7763288aa4a
     01e207725a0ae7bc95452fef3743f6083deb10cd23e2881e8d9307fc2f43bce1
     a97e
   control (141):
     50514452010000012c008200000001d0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0
     e1e2e3e4e5e6e7e1d90c86474574e0e51e82d8a29938896b0999e827138f8f45
     2f21e044d9809f65a013cfad8981be94c1354178b3e03dd518c28bcbaab962aa
     45246e446de7763288aa4a01e207725a0ae7bc95452fef3743f6083deb10cd23
     e2881e8d9307fc2f43bce1a97e
   response plaintext, padded to 256 (256, SHA-256):
     a215df14b59d272b506224ed1f6ab5956be2bf189f847dfac4f8649c5f94d99e
   full response wire (304, SHA-256):
     33c081503d5ead4061a30d3f095fc9f226b8c01c3bbffa8fc6f4d9b15087de5c

Denis                   Expires 22 December 2026               [Page 54]
Internet-Draft                  DNSCrypt                       June 2026

   sha256(ticket) (32):
     fb196d81022c6b480f1340c80987088a85145194c18441928a4ae8e5a153536c
   resumed shared-key (32):
     e61f03acb2ee2ef01b952a0c312c60653267d47a2766fcfd804747fdf2fe789f
   resumed encrypted-query (272, SHA-256):
     60323805036492350ee442ee4dcb097597fb586e3f2c8a1f26feb9cdd0409b29
   resume query wire (424, SHA-256):
     34be2e331b4d7c7e808e968c5efc9f25675a9de9064cb33f7c66950e0e4e6db7
   resume response wire, no new ticket (112, SHA-256):
     2bf202dd3f33d38854450e70a02bd1a317a23bf6d79c5dae406787c9c5f34f52

15.  Normative References

   [FIPS203]  National Institute of Standards and Technology, "Module-
              Lattice-Based Key-Encapsulation Mechanism Standard",
              FIPS 203, August 2024,
              <https://doi.org/10.6028/NIST.FIPS.203>.

   [RFC1035]  Mockapetris, P., "Domain names - implementation and
              specification", STD 13, RFC 1035, DOI 10.17487/RFC1035,
              November 1987, <https://www.rfc-editor.org/info/rfc1035>.

   [RFC2119]  Bradner, S., "Key words for use in RFCs to Indicate
              Requirement Levels", BCP 14, RFC 2119,
              DOI 10.17487/RFC2119, March 1997,
              <https://www.rfc-editor.org/info/rfc2119>.

   [RFC8174]  Leiba, B., "Ambiguity of Uppercase vs Lowercase in RFC
              2119 Key Words", BCP 14, RFC 8174, DOI 10.17487/RFC8174,
              May 2017, <https://www.rfc-editor.org/info/rfc8174>.

   [RFC8439]  Nir, Y. and A. Langley, "ChaCha20 and Poly1305 for IETF
              Protocols", RFC 8439, DOI 10.17487/RFC8439, June 2018,
              <https://www.rfc-editor.org/info/rfc8439>.

   [RFC7766]  Dickinson, J., Dickinson, S., Bellis, R., Mankin, A., and
              D. Wessels, "DNS Transport over TCP - Implementation
              Requirements", RFC 7766, DOI 10.17487/RFC7766, March 2016,
              <https://www.rfc-editor.org/info/rfc7766>.

   [RFC5358]  Damas, J. and F. Neves, "Preventing Use of Recursive
              Nameservers in Reflector Attacks", BCP 140, RFC 5358,
              DOI 10.17487/RFC5358, October 2008,
              <https://www.rfc-editor.org/info/rfc5358>.

Denis                   Expires 22 December 2026               [Page 55]
Internet-Draft                  DNSCrypt                       June 2026

   [RFC9000]  Iyengar, J., Ed. and M. Thomson, Ed., "QUIC: A UDP-Based
              Multiplexed and Secure Transport", RFC 9000,
              DOI 10.17487/RFC9000, May 2021,
              <https://www.rfc-editor.org/info/rfc9000>.

   [RFC7748]  Langley, A., Hamburg, M., and S. Turner, "Elliptic Curves
              for Security", RFC 7748, DOI 10.17487/RFC7748, January
              2016, <https://www.rfc-editor.org/info/rfc7748>.

   [RFC4291]  Hinden, R. and S. Deering, "IP Version 6 Addressing
              Architecture", RFC 4291, DOI 10.17487/RFC4291, February
              2006, <https://www.rfc-editor.org/info/rfc4291>.

   [RFC1918]  Rekhter, Y., Moskowitz, B., Karrenberg, D., de Groot, G.
              J., and E. Lear, "Address Allocation for Private
              Internets", BCP 5, RFC 1918, DOI 10.17487/RFC1918,
              February 1996, <https://www.rfc-editor.org/info/rfc1918>.

   [I-D.connolly-cfrg-xwing-kem]
              Connolly, D., Schwabe, P., and B. Westerbaan, "X-Wing:
              general-purpose hybrid post-quantum KEM", Work in
              Progress, Internet-Draft, draft-connolly-cfrg-xwing-kem-
              10, 2 March 2026, <https://datatracker.ietf.org/doc/html/
              draft-connolly-cfrg-xwing-kem-10>.

   [RFC5869]  Krawczyk, H. and P. Eronen, "HMAC-based Extract-and-Expand
              Key Derivation Function (HKDF)", RFC 5869,
              DOI 10.17487/RFC5869, May 2010,
              <https://www.rfc-editor.org/info/rfc5869>.

   [RFC6891]  Damas, J., Graff, M., and P. Vixie, "Extension Mechanisms
              for DNS (EDNS(0))", STD 75, RFC 6891,
              DOI 10.17487/RFC6891, April 2013,
              <https://www.rfc-editor.org/info/rfc6891>.

Author's Address

   Frank Denis
   Individual Contributor
   Email: fde@00f.net

Denis                   Expires 22 December 2026               [Page 56]