Kemeleon Encodings
draft-irtf-cfrg-kemeleon-01
This document is an Internet-Draft (I-D) that has been submitted to the Internet Research Task Force (IRTF) stream.
This I-D is not endorsed by the IETF and has no formal standing in the
IETF standards process.
| Document | Type | Active Internet-Draft (cfrg RG) | |
|---|---|---|---|
| Authors | Felix Günther , Douglas Stebila , Shannon Veitch | ||
| Last updated | 2026-01-16 | ||
| Replaces | draft-veitch-kemeleon | ||
| RFC stream | Internet Research Task Force (IRTF) | ||
| Intended RFC status | Informational | ||
| Formats | |||
| Additional resources | Mailing list discussion | ||
| Stream | IRTF state | Active RG Document | |
| Consensus boilerplate | Unknown | ||
| Document shepherd | (None) | ||
| IESG | IESG state | I-D Exists | |
| Telechat date | (None) | ||
| Responsible AD | (None) | ||
| Send notices to | (None) |
draft-irtf-cfrg-kemeleon-01
Network Working Group F. Günther
Internet-Draft IBM Research Europe - Zurich
Intended status: Informational D. Stebila
Expires: 20 July 2026 University of Waterloo
S. Veitch
ETH Zurich
16 January 2026
Kemeleon Encodings
draft-irtf-cfrg-kemeleon-01
Abstract
This document specifies Kemeleon encoding algorithms for encoding ML-
KEM encapsulation keys and ciphertexts as random bytestrings.
Kemeleon encodings provide obfuscation of encapsulation keys and
ciphertexts, relying on module LWE assumptions.
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://ssveitch.github.io/draft-kemeleon/draft-irtf-cfrg-
kemeleon.html. Status information for this document may be found at
https://datatracker.ietf.org/doc/draft-irtf-cfrg-kemeleon/.
Source for this draft and an issue tracker can be found at
https://github.com/ssveitch/draft-kemeleon.
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 20 July 2026.
Günther, et al. Expires 20 July 2026 [Page 1]
Internet-Draft Kemeleon January 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 . . . . . . . . . . . . . . . . . . . . . . . . 2
2. Conventions and Definitions . . . . . . . . . . . . . . . . . 3
3. Notation / ML-KEM Background . . . . . . . . . . . . . . . . 3
4. Kemeleon Encoding . . . . . . . . . . . . . . . . . . . . . . 4
4.1. Common Functions . . . . . . . . . . . . . . . . . . . . 4
4.2. Encoding Encapsulation Keys . . . . . . . . . . . . . . . 6
4.3. Encoding Ciphertexts . . . . . . . . . . . . . . . . . . 7
4.4. Summary of Properties . . . . . . . . . . . . . . . . . . 8
5. Additional Considerations for Applications . . . . . . . . . 8
5.1. Smaller Outputs from Rejection Sampling . . . . . . . . . 8
5.2. Deterministic Encoding . . . . . . . . . . . . . . . . . 10
5.3. Relation to Hash-to-Curve . . . . . . . . . . . . . . . . 10
5.4. Modifying ML-KEM Algorithms . . . . . . . . . . . . . . . 10
6. Security Considerations . . . . . . . . . . . . . . . . . . . 11
6.1. Computational Assumptions . . . . . . . . . . . . . . . . 11
6.2. Randomness Sampling . . . . . . . . . . . . . . . . . . . 11
6.3. Timing Side-Channels . . . . . . . . . . . . . . . . . . 12
7. IANA Considerations . . . . . . . . . . . . . . . . . . . . . 12
8. References . . . . . . . . . . . . . . . . . . . . . . . . . 12
8.1. Normative References . . . . . . . . . . . . . . . . . . 12
8.2. Informative References . . . . . . . . . . . . . . . . . 12
Acknowledgments . . . . . . . . . . . . . . . . . . . . . . . . . 13
Authors' Addresses . . . . . . . . . . . . . . . . . . . . . . . 13
1. Introduction
ML-KEM [FIPS203] is a post-quantum key-encapsulation mechanism (KEM)
recently standardized by NIST, Many applications are transitioning
from classical Diffie-Hellman (DH) based solutions to constructions
based on ML-KEM. The use of Elligator and related Hash-to-Curve
[RFC9380] algorithms are ubiquitous in DH-based protocols where DH
shares are required to be encoded as, and look indistinguishable
from, random bytestrings. For example, applications using Elligator
include protocols used for censorship circumvention in Tor [OBFS4],
password-authenticated key exchange (PAKE) protocols [CPACE]
Günther, et al. Expires 20 July 2026 [Page 2]
Internet-Draft Kemeleon January 2026
[OPAQUE], and private set intersection (PSI) [ECDH-PSI].
For the post-quantum transition, an analogous encoding for (ML-)KEM
encapsulation keys and ciphertexts to random bytestrings is required.
This document specifies such an encoding, Kemeleon, for ML-KEM
encapsulation keys and ciphertexts. Kemeleon was introduced in
[GSV24] for building an (post-quantum) "obfuscated" KEM whose
encapsulation keys and ciphertexts are indistinguishable from random.
This document specifies a version of the Kemeleon encoding that
avoids any failure probability, as well as an alternate version that
trades some failure probability for smaller encoding size.
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. Notation / ML-KEM Background
A KEM consists of three algorithms:
* KeyGen() -> (ek, dk): A probabilistic key generation algorithm
that, with no input, generates an encapsulation key ek and a
decapsulation key dk.
* Encaps(ek) -> (c, K): A probabilistic encapsulation algorithm that
takes as input an encapsulation key ek, and outputs a ciphertext
ct and shared secret K.
* Decaps(dk, c) -> K: A decapsulation algorithm that takes as input
a decapsulation key dk and ciphertext c, and outputs a shared
secret K.
The following variables and functions are adopted from [FIPS203]:
* q = 3329, n = 256
* Compress_d : x -> round((2^d/q)*x) mod 2^d (Equation 4.7)
* Decompress_d : y -> round((q/2^d)*y) (Equation 4.8)
* remaining parameters k, d_u, d_v, etc. are defined by the
respective ML-KEM parameter set -- this document writes du and dv
in place of d_u, d_v in pseudocode
Günther, et al. Expires 20 July 2026 [Page 3]
Internet-Draft Kemeleon January 2026
ML-KEM.KeyGen() (Section 7.1 [FIPS203]) produces an encapsulation
key, ek and a decapsulation key, dk. Encapsulation keys consist of
byte-encoded vectors of coefficients in Z_q, where each coefficient
is encoded in 12 bits, together with a 32-byte seed for generating
the matrix A. ML-KEM.Encaps(ek) (Section 7.2 [FIPS203]) produces
ciphertexts consisting of byte-encoded compressed vectors of
cofficients, where each coefficient in Z_q is compressed by a certain
number of bits (depending on the ML-KEM parameter set).
The following terms and notation are used throughout this document:
* a[i] denotes the ith position of a vector a of coefficients
* concat(x0, ..., xN): returns the concatenation of bytestrings.
4. Kemeleon Encoding
At a high level, the constructions in this document instantiate the
following functions:
* EncodeEk(ek) -> eek is the (possibly randomized) encoding
algorithm that on input an encapsulation key, outputs an
obfuscated encapsulation key or an error.
* DecodeEk(eek) -> ek is the deterministic decoding algorithm that
on input an obfuscated encapsulation key, outputs an encapsulation
key.
* EncodeCtxt(c) -> ec is the (possibly randomized) encoding
algorithm that on input a ciphertext, outputs an obfuscated
ciphertext or an error.
* DecodeCtxt(ec) -> c is the deterministic decoding algorithm that
on input an obfuscated ciphertext, outputs a ciphertext.
4.1. Common Functions
The following function maps a vector of length n of coefficients
modulo q to a large integer. Applying the technique from [ELL2],
where r is the large integer resulting from accumulating
coefficients, we then choose m at random from [0,floor((2^(b+t)-
r)/(q^n))], where b = ceil(n*log2(q)) and t is a security parameter,
and return r + m*q^(n). Notably, the random value m need not be
transmitted alongside the encoded values. This results in encoded
values whose statistical distance from uniform is at most 2^-t.
Notably, this statistical distance is unconditional; we hence fix
t=128. This results in the encoding size increasing by t bits, i.e.,
16 bytes.
Günther, et al. Expires 20 July 2026 [Page 4]
Internet-Draft Kemeleon January 2026
VectorEncode(a):
r = 0
t = 128
b = ceil(n*log2(q))
for i from 1 to n:
r += q^(i-1)*a[i]
m <--$ [0,...,floor((2^(b+t)-r)/(q^(n)))]
return r + m*q^n
VectorDecode(r):
r = r % q^n
for i from 1 to n:
a[i] = r % q
r = r // q
return a
The following algorithm samples an uncompressed pre-image of a
coefficient c at random, where u is the decompressed value of c. It
must take as input values of u that are output from Decompress_d.
The mapping is based on the Compress_d, Decompress_d algorithms from
(Section 4.2.1 [FIPS203]).
Günther, et al. Expires 20 July 2026 [Page 5]
Internet-Draft Kemeleon January 2026
SamplePreimage(d,u,c):
if d == 10:
if Compress_d(u + 2) == c:
rand <--$ [-1,0,1,2]
else if Compress_d(u - 2) == c:
rand <--$ [-2,-1,0,1]
else:
rand <--$ [-1,0,1]
return u + rand
if d == 11:
if Compress_d(u + 1) == c:
rand <--$ [0,1]
else if Compress_d(u - 1) == c:
rand <--$ [-1,0]
else:
rand = 0
return u + rand
if d == 5:
if u == 0:
rand <--$ [-52,...,52]
else if u <= 1560:
rand <--$ [-51,...,52]
else:
rand <--$ [-52,...,51]
return u + rand
if d == 4:
if u == 0:
rand <--$ [-104,...,104]
else if u <= 1456:
rand <--$ [-103,...,104]
else:
rand <--$ [-104,...,103]
return u + rand
else:
return err
4.2. Encoding Encapsulation Keys
The following algorithms encode ML-KEM encapsulation keys as random
bytestrings. rho is the public seed used to generate the public
matrix A [FIPS203]. This is already a random 32-byte string, so it
is returned alongside the encoded value of t. t is a vector of k
polynomials with n coefficients. We treat each polynomial in t as a
vector of n coefficient, for which we apply VectorEncode. From this,
we obtain k values that are then concatenated.
Günther, et al. Expires 20 July 2026 [Page 6]
Internet-Draft Kemeleon January 2026
Kemeleon.EncodeEk(ek = (t, rho)):
for i in range(k):
r_i = VectorEncode(t[i])
r = concat(r_1,...,r_k)
return concat(r,rho)
Kemeleon.DecodeEk(eek):
r_1,..,r_k,rho = eek # rho and each r_i is fixed length
t = []
for i in range(k):
t_i = VectorDecode(r_i)
t.append(t_i)
return (t, rho)
4.3. Encoding Ciphertexts
ML-KEM ciphertexts consist of two components: c_1, a vector of k
polynomials with n coefficients mod 2^du, and c_2, a polynomial with
n coefficients mod 2^dv. The coefficients of these polynomials are
not uniformly distributed, as a result of the compression step in
encapsulation. The following encoding function decompresses and
recovers a random preimage of this compression step in order to
recover the uniform distribution of coefficients. Then, the same
vector encoding step used for encapsulation keys can be applied.
Kemeleon.EncodeCtxt(c = (c_1,c_2)):
u = Decompress_du(c_1)
for i from 1 to k*n:
u[i] = SamplePreimage(du,u[i],c_1[i])
v = Decompress_dv(c_2)
for i from 1 to n:
v[i] = SamplePreimage(dv,v[i],c_2[i])
for i in range(k)
r_i = VectorEncode(u[i])
r_(k+1) = VectorEncode(v)
r = concat(r_0,...,r_(k+1))
return r
Kemeleon.DecodeCtxt(r):
r_0,...,r_(k+1) = r # each r_i is fixed length
for i in range(k):
u[i] = VectorDecode(r_i)
v = VectorDecode(r_(k+1))
c_1 = Compress_du(u)
c_2 = Compress_dv(v)
return (c_1,c_2)
Günther, et al. Expires 20 July 2026 [Page 7]
Internet-Draft Kemeleon January 2026
4.4. Summary of Properties
+=======================+=====================+=====================+
| Algorithm / Parameter | Output size (bytes) | Success |
| | | probability |
+=======================+=====================+=====================+
| Kemeleon - ML-KEM512 | ek: 814, ctxt: 1172 | ek: 1.00, |
| | | ctxt: 1.00 |
+-----------------------+---------------------+---------------------+
| Kemeleon - ML-KEM768 | ek: 1204, ctxt: | ek: 1.00, |
| | 1562 | ctxt: 1.00 |
+-----------------------+---------------------+---------------------+
| Kemeleon - ML-KEM1024 | ek: 1594, ctxt: | ek: 1.00, |
| | 1953 | ctxt: 1.00 |
+-----------------------+---------------------+---------------------+
Table 1: Summary of Kemeleon Properties
5. Additional Considerations for Applications
This section contains additional considerations and comments related
to using Kemeleon encodings in different applications.
5.1. Smaller Outputs from Rejection Sampling
In applications willing to incur some probability of failure in
encoding, a variant of the encoding algorithm that does not add the
additional m value can be used. This results in smaller output sizes
for public keys and ciphertexts. However, in this case it is no
longer feasible to parallelize the encoding of the k polynomials;
these must be treated as a single vector of k*n coefficients in order
to achieve a reasonable rate of rejection. Therefore, this approach
also requires arithmetic over larger integers (up to 1872B integers
for ML-KEM1024). In particular, the following algorithms can be used
instead of VectorEncode and VectorDecode above.
VectorEncode(a,k):
r = 0
for i from 1 to k*n:
r += q^(i-1)*a[i]
if msb(r) == 1:
return err
else:
return r
Günther, et al. Expires 20 July 2026 [Page 8]
Internet-Draft Kemeleon January 2026
VectorDecode(r,k):
for i from 1 to k*n:
a[i] = r % q
r = r // q
return a
The encoding algorithms for public keys should handle errors
accordingly, returning an error if VectorEncode returns an error.
For ciphertexts, the second ciphertext component need not be
decompressed, and rejection sampling can be used to retain uniformity
instead.
Kemeleon.EncodeCtxt(c = (c_1,c_2)):
u = Decompress_du(c_1)
for i from 1 to k*n:
u[i] = SamplePreimage(du,u[i],c_1[i])
r = VectorEncode(u)
if r == err:
return err
for i from 1 to n:
if c_2[1] == 0:
return err with prob. 1/ceil(q/(2^dv))
return concat(r,c_2)
Kemeleon.DecodeCtxt(ec):
r,c_2 = ec # c_2 is fixed length
u = VectorDecode(r)
c_1 = Compress_du(u)
return (c_1,c_2)
This variant of the encoding is as described in the original work
[GSV24], and has the following properties.
+=============+=============+=============+===================+
| Algorithm / | Output size | Success | Additional |
| Parameter | (bytes) | probability | considerations |
+=============+=============+=============+===================+
| Kemeleon - | ek: 781, | ek: 0.56, | Large int (750B) |
| ML-KEM512 | ctxt: 877 | ctxt: 0.51 | arithmetic |
+-------------+-------------+-------------+-------------------+
| Kemeleon - | ek: 1156, | ek: 0.83, | Large int (1150B) |
| ML-KEM768 | ctxt: 1252 | ctxt: 0.77 | arithmetic |
+-------------+-------------+-------------+-------------------+
| Kemeleon - | ek: 1530, | ek: 0.62, | Large int (1500B) |
| ML-KEM1024 | ctxt: 1658 | ctxt: 0.57 | arithmetic |
+-------------+-------------+-------------+-------------------+
Table 2: Summary of Alternate Encoding Properties
Günther, et al. Expires 20 July 2026 [Page 9]
Internet-Draft Kemeleon January 2026
5.2. Deterministic Encoding
The randomness used in Kemeleon ciphertext encodings MAY be derived
in a deterministic manner. To do so, following a call to Encap which
returns a KEM key K and a ciphertext c, the following steps can be
taken:
* Using a key derivation function (KDF), derive from the key K a new
key K' and a seed for randomness rnd.
* The seed rnd can be used to generate the randomness required when
encoding the ciphertext c.
* Use K' in place of K wherever applicable in the remainder of the
protocol/system.
* Upon any call to Decap, apply the same KDF to derive the new key
K', as required.
Deriving a new KEM key for use in the remainder of a system is
crucial in order to ensure key separation (i.e., the implementation
MUST NOT use the original key K to derive randomness and for other
purposes).
The randomness used to encode an encapsulation key MAY be stored
alongside the corresponding decapsulation key, if it is subsequently
needed. See Section 6.2 for relevant discussion on keeping this
randomness secret.
5.3. Relation to Hash-to-Curve
While the functionality of Kemeleon is similar to hash-to-curve
[RFC9380] (mapping arbitrary byte strings to public keys/
ciphertexts), the applications where hash-to-curve is used do not
immediately follow in the KEM-based setting because having such an
encapsulation key (without dk) or ciphertext (without dk or ek) does
not appear to provide the same functionality, since it is not clear
how to continue working with the element in the same way that can be
done with an elliptic curve point.
5.4. Modifying ML-KEM Algorithms
In applications that _only_ require Kemeleon-encoded values _and_
where the underlying ML-KEM implementation can be modified, the
ciphertext encoding algorithm (and ML-KEM encapsulation/decapsulation
algorithms) MAY be adapted as follows for improved efficiency. In
particular, the compression step in the ML-KEM encapsulation
algorithm can be omitted, and therefore, the decompression step in
Günther, et al. Expires 20 July 2026 [Page 10]
Internet-Draft Kemeleon January 2026
the Kemeleon algorithm can be omitted. In the implementation of ML-
KEM, the compression step (lines 22-23 of Algorithm 14 [FIPS203]) and
corresponding decompression step (lines 3-4 of Algorithm 15
[FIPS203]) can be omitted from the encapsulation/decapsulation
algorithms in ML-KEM. In this case, the Kemeleon encoding algorithm
for ciphertexts would omit the Decompress and SamplePreimage steps
and immediately apply VectorEncode:
Kemeleon.EncodeCtxt(c = (c_1,c_2)):
w = [c_1,c_2] # treat c_1,c_2 as a singular vector of (k+1)*n coefficients
r = VectorEncode(w,k+1)
return r
Decoding is adapted analogously.
Kemeleon.DecodeCtxt(ec):
w = VectorDecode(r,k+1)
c_1,c_2 = w # c_1, c_2 are fixed length
return (c_1,c_2)
6. Security Considerations
This section contains additional security considerations about the
Kemeleon encodings described in this document.
6.1. Computational Assumptions
In general, the obfuscation properties of the Kemeleon encodings
depend on module LWE assumptions similar to those underlying the IND-
CCA security of ML-KEM; see [GSV24] for the detailed security
analysis of the original Kemeleon encoding. In particular, the
notions of public key and ciphertext uniformity capture the
indistinguishability of Kemeleon-encoded encapsulation keys and
ciphertexts from random bitstrings, respectively. Both require the
module LWE assumption to hold in order for Kemeleon to maintain its
uniformity properties. Furthermore, distinguishing a pair of a
Kemeleon-encoded encapsulation key and a Kemeleon-encoded ciphertext
from uniformly random bitstrings also reduces to a module LWE
assumption.
6.2. Randomness Sampling
Both encapsulation key and ciphertext encodings in the Kemeleon
encoding are randomized. The randomness (or seed used to generate
randomness) used in Kemeleon encodings MUST be kept secret. In
particular, public randomness enables distinguishing a Kemeleon-
encoded value from a random bytestring: Decoding the value in
question and re-encoding it with the public randomness will yield the
Günther, et al. Expires 20 July 2026 [Page 11]
Internet-Draft Kemeleon January 2026
original value if it was Kemeleon-encoded.
6.3. Timing Side-Channels
Beyond timing side-channel considerations for ML-KEM itself, care
should be taken when using Kemeleon encodings. Algorithms required
to perform large integer arithmetic may leak information via timing.
Additionally, rejecting and re-generating encapsulation keys or
ciphertexts may leak information about the use of Kemeleon encodings,
as might the overhead of the encoding itself.
7. IANA Considerations
This document has no IANA actions.
8. References
8.1. Normative References
[ELL2] Tibouchi, M., "Elligator Squared: Uniform Points on
Elliptic Curves of Prime Order as Uniform Random Strings",
2014, <https://eprint.iacr.org/2014/043>.
[FIPS203] "Module-lattice-based key-encapsulation mechanism
standard", National Institute of Standards and Technology
(U.S.), DOI 10.6028/nist.fips.203, August 2024,
<https://doi.org/10.6028/nist.fips.203>.
[GSV24] Günther, F., Stebila, D., and S. Veitch, "Obfuscated Key
Exchange", 2024, <https://eprint.iacr.org/2024/1086>.
[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/rfc/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/rfc/rfc8174>.
[RFC9380] Faz-Hernandez, A., Scott, S., Sullivan, N., Wahby, R. S.,
and C. A. Wood, "Hashing to Elliptic Curves", RFC 9380,
DOI 10.17487/RFC9380, August 2023,
<https://www.rfc-editor.org/rfc/rfc9380>.
8.2. Informative References
Günther, et al. Expires 20 July 2026 [Page 12]
Internet-Draft Kemeleon January 2026
[CPACE] Abdalla, M., Haase, B., and J. Hesse, "CPace, a balanced
composable PAKE", Work in Progress, Internet-Draft, draft-
irtf-cfrg-cpace-17, 18 December 2025,
<https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-
cpace-17>.
[ECDH-PSI] Wang, Y., ChangWenting, Lu, Y., Hong, C., and J. Peng,
"PSI based on ECDH", Work in Progress, Internet-Draft,
draft-ecdh-psi-00, 21 October 2024,
<https://datatracker.ietf.org/doc/html/draft-ecdh-psi-00>.
[OBFS4] "obfs4 (The obfourscator)", n.d.,
<https://gitlab.torproject.org/tpo/anti-censorship/
pluggable-transports/lyrebird/-/blob/HEAD/doc/
obfs4-spec.txt>.
[OPAQUE] Bourdrez, D., Krawczyk, H., Lewi, K., and C. A. Wood, "The
OPAQUE Augmented PAKE Protocol", Work in Progress,
Internet-Draft, draft-irtf-cfrg-opaque-18, 21 November
2024, <https://datatracker.ietf.org/doc/html/draft-irtf-
cfrg-opaque-18>.
Acknowledgments
Thanks to Michael Rosenberg, John Mattsson, and Stanislaw Jarecki for
contributions to this document and helpful discussions.
Authors' Addresses
Felix Günther
IBM Research Europe - Zurich
Email: mail@felixguenther.info
Douglas Stebila
University of Waterloo
Email: dstebila@uwaterloo.ca
Shannon Veitch
ETH Zurich
Email: shannon.veitch@inf.ethz.ch
Günther, et al. Expires 20 July 2026 [Page 13]