Rot256 : Cryptography & Other Random Bits.

Sender hiding Box

The NaCl Box

The NaCl box provides a very simple way to asymmetrically encrypt and authenticate messages: the message is encrypted by doing Diffie-Hellman between the parties long-term keys and encrypting using an AEAD. One of the central features of Box is that it provides deniability:

“The crypto_box function is not meant to provide non-repudiation. On the contrary: the crypto_box function guarantees repudiability. A receiver can freely modify a boxed message, and therefore cannot convince third parties that this particular message came from the sender. The sender and receiver are nevertheless protected against forgeries by other parties.”

This is in stark contrast to e.g. PGP signed and encrypted messages, where a non-reputable signature is encrypted. Non-repudiation / deniability is often a desirable property, in particular when the primary application is simply private and authenticated channels between individuals.

However one notable drawback of NaCl box is that hiding the identity of the sending party is not easy: either the recipient needs to expect a message from the sender and hence the senders public key can be omitted, or, the public key of the sender must be passed along with the message. The obvious way to fix this is to encrypt twice:

  1. Encrypt using your long-term secret key \(x\) and the recipients public key \(y'\).
  2. Then encrypt again using an ephemeral secret key \( x_{ephm} \) and the recipients public key \(y'\).

\[ y_{ephm} \ \text{||} \ \texttt{BoxEnc}(x_{ephm}, y', y \ \text{||} \ \texttt{BoxEnc}(x, y', m)) \]

However it is not particularly elegant nor efficient. Even though the efficiency can be improved somewhat (e.g. by deriving the nonces from \( y_{ephm} \)), the goal of this post is to provide a more elegant solution to the problem.

A sender hiding Box

Size: In terms of size the message is only 8 bytes larger than the existing box when including the public key. Hence it is significantly smaller than the application of nested boxes used to hide the senders identity.

Computation: Computation-wise the new box requires two exponentiations for both encryption/decryption: which is more efficient that the ephemeral key solution which requires 3 exponentiations during encryption and 2 during decryption.


The scheme is incredibly simple: the core trick is to re-randomized the senders public key and to include the re-randomizing scalar inside the encrypted message payload. We let \( x, x' \) be the secret keys of the sender/reciever respectively and let \( y = g^x, y' = g^{x'} \) be the corresponding public keys. The message to transmit is denoted \( m \). We require a prime-order group (e.g. Ristretto construction on top of Curve25519), where Computational Diffie-Hellman (CDH) is intractable. Additional we require the following primitives:

We let \( 0^\ell \) denote the all zero string of \( \ell \) bytes.

\( \texttt{Encrypt}(y', x, m) \):

Sample a random scalar \( \alpha \overset$\leftarrow \mathbb{Z}_{|\mathbb{G}|} \), compute \( h \leftarrow g^{\alpha x} \). Obtain the shared secret \( \sigma \leftarrow y'^{\alpha x} \), compute the key \( k \leftarrow \texttt{KDF}(\sigma) \). Compute the ciphertext \( c \leftarrow \texttt{Seal}(k, 0^\ell, h, \alpha \text{||} m) \). Output \( h \text{||} c \).

\( \texttt{Decrypt}(x', h \text{||} c) \):

Obtain the shared secret \( \sigma \leftarrow h^{x'} \), compute the key \( k \leftarrow \texttt{KDF}(\sigma) \). Decrypt and parse the ciphertext \( \alpha \text{||} m \leftarrow \texttt{Open}(k, 0^\ell, h, c)\). Fail if \( \alpha = 0 \), otherwise compute \( y_{sender} \leftarrow h^{\alpha^{-1}} \). Output \( (y_{sender}, m) \).


By construction: holding \( x \) and \( y' \) produce a message \( m \) from \( y' \) as follows: Pick \( \alpha \overset$\leftarrow \mathbb{Z}_{|\mathbb{G}|} \) Compute \( h \leftarrow y'^{\alpha} \) and obtain the shared secret \( \sigma \leftarrow h^{x} \), compute the key \( k \leftarrow \texttt{KDF}(\sigma) \). Compute the ciphertext \( c \leftarrow \texttt{Seal}(k, 0^\ell, h, \alpha \text{||} m) \), output \( h \text{||} c \) as the encryption.

It is easily checked that this distribution is identical to that of the original encryption procedure.


There is a trivial reduction to CDH (in the RO model): the intuition is that computing \( g^{x x' \alpha} \) for any known \( \alpha \neq 0 \) cannot be easier than computing \( g^{x x'} \) which is exactly CDH: modelling the \( \texttt{KDF} \) as a random oracle, suppose there exists an adversary \( \mathcal{A} \) which given \( y, y' \) produces a ciphertext \( h \text{||} c\) which decrypts to \( (y, m) \) under \( x' \). Then use \( \mathcal{A} \) to solve CDH as follows:

Given the challenge \( g^a, g^b \) compute \( g^{ab} \) as follows: let \( y = g^a \), \( y' = g^b \), run \( \mathcal{A} \) on \( y, y' \). We are given \( h, m \) and using the RO we obtain the query/response pair \( \sigma, k \) with \( \sigma = h^{b} \) from the \( \texttt{KDF} \) application. We use \( k \) to decrypt \( c \) thus recovering \( \alpha \in \mathbb{Z}_{\mathbb{G}} \) st. \( h^{\alpha^{-1}} = y \). Hence \( h = y^{\alpha} \) and \( \sigma = y^{\alpha b} = g^{a b \alpha} \). Output \( \sigma^{\alpha^{-1}} \).

Additional considerations

Protection against poor random sources

For simplicity the above description samples \( \alpha \overset$\leftarrow \mathbb{Z}_{|\mathbb{G}|} \), however it is advantageous to generate \( \alpha \) ‘synthetically’, by first obtaining \( r \overset$\leftarrow \{0, 1\}^{\kappa} \), then derive \( \alpha \leftarrow \texttt{PRNG}(x \text{||} y' \text{||} m \text{||} r) \) in order to provide protection against flawed random sources. Even in the case of a complete RNG failure (e.g. the RNG outputs a constant), it would only leak when then same message was sent to the same recipient and does not enable the adversary to forge messages between the sender & receiver pair due to nonce-reuse in the AEAD scheme.

The existing NaCl Box provides no such protections.

Limited protection against later key compromise

Like PGP, but unlike the plain (non-nested) NaCl Box, a payload encrypted by the sender using \( x \), cannot be decrypted using \( x \) and \( y' \). This is essentially the best you can do without additional communication to exchange ephemeral keys, or, using more complex cryptography like puncturable encryption.

Retrieving the sender identity only when needed

If the receiver is uninterested in learning the identity of the sender, he can simply omit the \( h^{\alpha^{-1}} \) computation and the overall scheme essentially becomes equivalent to the original Box where encryption is done using an ephemeral secret key and a constant nonce.

The computation cost is the same as the ‘ephemeral’ unauthenticated Box scenario.