BN is the set of all N-byte strings. B is the set of all byte strings of any length.
randomFr()→Fr: a function that uniformly samples a random element of Baby Jubjub's scalar field.
secret∈Fr: A secret, random field element from which the protocol is "seeded".
encapsulatedSecret∈G: A Baby Jubjub curve point that "encapsulates" ephemeralKey. Specifically, it's the sender's portion of a non-interactive Diffie-Hellman exchange (see below).
sharedSecret∈G: a shared "secret" Baby Jubjub curve point used to derive ephemeralKey using HKDF. It is shared between the sender and the receiver via a non-interactive Diffie-Hellman exchange (see below).
ephemeralKey∈B32: A 32-byte ephemeral encryption key used for the symmetric part of the encryption scheme
HKDF(ikm)→B32: A key derivation function that derives a 32-byte key from an arbitrary-length byte string representing the "input keying material", or ikm for short. We use it to derive ephemeralKey from sharedSecret. We use HKDF as defined by [RFC-5869](https://datatracker.ietf.org/doc/html/rfc5869) and instantiate it over SHA256.
ChaCha20Poly1305: The symmetric encryption part of our HPKE scheme. In case it's not clear from the name, we use the ChaCha20Poly1305 encryption scheme. It has two methods:
seal(key,nonce,plaintext)→B: encrypts the given plaintextusing the given 32-byte key and 12-byte nonce.
open(key,nonce,ciphertext)→B∪⊥: attempts to decrypt the given ciphertextusing the given nonce and ciphertext. If it fails, it returns an error (⊥).
deriveBaseNonce(sharedSceret)→B12: a function that derives a 12-byte nonce for ChaCha20Poly1305 from sharedSecret.
the message msg
and the recipient's canonical address R, which we use as their public key in this scheme (the corresponding secret key is their viewing key vk)
secret←randomFr()
encapsulatedSecret←secret×G (the ga part of Diffie-Hellman)
sharedSecret←secret×R (the gab part of Diffie-Hellman, where b is the recipient's vk)
ephemeralKey←HKDF(encapsulatedSecret ∣∣ sharedSecret)
nonce←deriveBaseNonce(sharedSecret)
plaintext←secret ∣∣ msg
ciphertext←ChaCha20Poly1305.seal(ephemeralKey,nonce,plaintext)
return (encapsulatedSecret,ciphertext)
the ciphertext ciphertext
the encapsulated secret encapsulatedSecret
the recipient's viewing key rvk
sharedSecret←rvk×encapsulatedSecret(the receiver's side of Diffie-Hellman,(ga)b=gab)
ephemeralKey←HKDF(encapsulatedSecret ∣∣ sharedSecret)
nonce←deriveBaseNonce(sharedSecret)
plaintext←ChaCha20Poly1305.open(ephemeralKey,nonce,ciphertext)
if plaintext=⊥, reject the message
otherwise, parse (secret,msg) from plaintext
expectedEncapsulatedSecret←secret×G (extra check requiring the sender to prove that they know secret)
if expectedEncapsulatedSecret=encapsulatedSecret, reject the message
otherwise, return msg