Nocturne
  • Introduction
    • Introduction
    • Protocol Overview
    • Compliance
  • Protocol Concepts
    • Keys and Stealth Addresses
    • Notes, Commitment Tree, Nullifiers, and JoinSplits
    • Deposits
    • Operations
  • Protocol Details
    • Algebraic Primitives & Notation
    • Keys & Key Derivation
    • Stealth Addresses
    • Signatures
    • Encodings
    • Commitment Tree
      • Subtree Update Circuit
    • JoinSplit Circuit
    • Note Encryption
    • Contracts
      • Deposit Manager
      • Teller
      • Handler
      • ETH Adapters
      • Canonical Address Registry
    • Offchain Actors
      • Deposit Screener
      • Bundler
      • Subtree Updater
  • Users
    • MetaMask Snap
    • FAQ
  • Developers
    • Contract Addresses
    • Trusted Setup
    • Security
    • Guardrails
    • Source Code
Powered by GitBook
On this page
  • Overview
  • Detailed Description
  1. Protocol Details

Note Encryption

PreviousJoinSplit CircuitNextContracts

Last updated 1 year ago

Within the nocturne protocol, notes created via a JoinSplit are encrypted and then published on-chain in a manner such that only the recipient's viewing key can decrypt it. This allows users to privately and trustlessly detect incoming notes.

Overview

Nocturne uses a variant of Hybrid Public Key Encryption (HPKE) to accomplish this. Before we describe Nocturne's scheme in detail, for context, we'll describe at a high level how HPKE works:

  1. The Sender...

    1. generates an ephemeral, single-use symmetric encryption key.

    2. uses the recipient's public key to "encapsulate" the symmetric encryption key. Only the recipient can "decapsulate" it. This mechanism is commonly referred to as a Key Encapsulation Module (KEM).

    3. encrypts the message using the symmetric key using an authenticated encryption scheme to prevent malicious actors from modifying the ciphertext in transit.

    4. sends encapsulated key along with the ciphertext to the recipient

  2. The recipient...

    1. attempts to decapsulate the encapsulated key. If it succeeds, they can recover the ephemeral secret key. If it fails, they reject the message.

    2. attempts to decrypt the message using the symmetric encryption key. If it fails, they reject the message.

Public key encryption allows us to encrypt a message to the recipient without sharing secret information but is slow. On the other hand, symmetric key encryption is fast but requires a secret to be shared between parties. HPKE gives us the best of both worlds, which is important because we need to encrypt many notes on resource-constrained devices, but we also have no trustless mechanism for sharing secret keys.

Detailed Description

Context out of the way, we'll now describe Nocturne's note encryption scheme in detail. First, some definitions:

  • BN\mathbb{B}^{N}BN is the set of all NNN-byte strings. B\mathbb{B}B is the set of all byte strings of any length.

  • randomFr()→Fr\text{randomFr}() \rightarrow \mathbb{F}_rrandomFr()→Fr​: a function that uniformly samples a random element of Baby Jubjub's scalar field.

  • secret∈Fr\text{secret} \in \mathbb{F}_rsecret∈Fr​: A secret, random field element from which the protocol is "seeded".

  • encapsulatedSecret∈G\text{encapsulatedSecret} \in \mathbb{G}encapsulatedSecret∈G: A Baby Jubjub curve point that "encapsulates" ephemeralKey\text{ephemeralKey}ephemeralKey. Specifically, it's the sender's portion of a non-interactive Diffie-Hellman exchange (see below).

  • sharedSecret∈G\text{sharedSecret} \in \mathbb{G}sharedSecret∈G: a shared "secret" Baby Jubjub curve point used to derive ephemeralKey\text{ephemeralKey}ephemeralKey using HKDF\text{HKDF}HKDF. It is shared between the sender and the receiver via a non-interactive Diffie-Hellman exchange (see below).

  • ephemeralKey∈B32\text{ephemeralKey} \in \mathbb{B}^{32}ephemeralKey∈B32: A 32-byte ephemeral encryption key used for the symmetric part of the encryption scheme

  • HKDF(ikm)→B32\text{HKDF}(\text{ikm}) \rightarrow \mathbb{B}^{32} 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\text{ikm}ikm for short. We use it to derive ephemeralKey\text{ephemeralKey}ephemeralKey from sharedSecret\text{sharedSecret}sharedSecret. We use HKDF as defined by [RFC-5869]() and instantiate it over SHA256.

  • ChaCha20Poly1305\text{ChaCha20Poly1305}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\text{seal}(\text{key}, \text{nonce}, \text{plaintext}) \rightarrow \mathbb{B}seal(key,nonce,plaintext)→B: encrypts the given plaintext\text{plaintext}plaintextusing the given 32-byte key\text{key}key and 12-byte nonce\text{nonce}nonce.

    • open(key,nonce,ciphertext)→B∪⊥\text{open}(\text{key}, \text{nonce}, \text{ciphertext}) \rightarrow \mathbb{B} \cup \botopen(key,nonce,ciphertext)→B∪⊥: attempts to decrypt the given ciphertext\text{ciphertext}ciphertextusing the given nonce\text{nonce}nonce and ciphertext\text{ciphertext}ciphertext. If it fails, it returns an error (⊥\bot⊥).

  • deriveBaseNonce(sharedSceret)→B12\text{deriveBaseNonce}(\text{sharedSceret}) \rightarrow \mathbb{B}^{12} deriveBaseNonce(sharedSceret)→B12: a function that derives a 12-byte nonce for ChaCha20Poly1305\text{ChaCha20Poly1305}ChaCha20Poly1305 from sharedSecret\text{sharedSecret}sharedSecret.

Spelled out, encryption takes as input:

  • the message msg\text{msg}msg

  • and the recipient's canonical address RRR, which we use as their public key in this scheme (the corresponding secret key is their viewing key vk\text{vk}vk)

And proceeds as follows:

  1. secret←randomFr()\text{secret} \leftarrow \text{randomFr}()secret←randomFr()

  2. encapsulatedSecret←secret×G\text{encapsulatedSecret} \leftarrow \text{secret} \times GencapsulatedSecret←secret×G (the gag^aga part of Diffie-Hellman)

  3. sharedSecret←secret×R\text{sharedSecret} \leftarrow \text{secret} \times RsharedSecret←secret×R (the gabg^{ab}gab part of Diffie-Hellman, where bbb is the recipient's vk\text{vk}vk)

  4. ephemeralKey←HKDF(encapsulatedSecret ∣∣ sharedSecret)\text{ephemeralKey} \leftarrow \text{HKDF}(\text{encapsulatedSecret}\ ||\ \text{sharedSecret})ephemeralKey←HKDF(encapsulatedSecret ∣∣ sharedSecret)

  5. nonce←deriveBaseNonce(sharedSecret)\text{nonce} \leftarrow \text{deriveBaseNonce}(\text{sharedSecret})nonce←deriveBaseNonce(sharedSecret)

  6. plaintext←secret ∣∣ msg\text{plaintext} \leftarrow \text{secret}\ ||\ \text{msg}plaintext←secret ∣∣ msg

  7. ciphertext←ChaCha20Poly1305.seal(ephemeralKey,nonce,plaintext)\text{ciphertext} \leftarrow \text{ChaCha20Poly1305.seal}(\text{ephemeralKey}, \text{nonce}, \text{plaintext})ciphertext←ChaCha20Poly1305.seal(ephemeralKey,nonce,plaintext)

  8. return (encapsulatedSecret,ciphertext)(\text{encapsulatedSecret}, \text{ciphertext})(encapsulatedSecret,ciphertext)

Decryption takes as input:

  • the ciphertext ciphertext\text{ciphertext}ciphertext

  • the encapsulated secret encapsulatedSecret\text{encapsulatedSecret}encapsulatedSecret

  • the recipient's viewing key rvk\text{rvk}rvk

And proceeds as follows:

  1. sharedSecret←rvk×encapsulatedSecret\text{sharedSecret} \leftarrow \text{rvk} \times \text{encapsulatedSecret}sharedSecret←rvk×encapsulatedSecret(the receiver's side of Diffie-Hellman,(ga)b=gab(g^a)^b = g^{ab}(ga)b=gab)

  2. ephemeralKey←HKDF(encapsulatedSecret ∣∣ sharedSecret)\text{ephemeralKey} \leftarrow \text{HKDF}(\text{encapsulatedSecret}\ ||\ \text{sharedSecret})ephemeralKey←HKDF(encapsulatedSecret ∣∣ sharedSecret)

  3. nonce←deriveBaseNonce(sharedSecret)\text{nonce} \leftarrow \text{deriveBaseNonce}(\text{sharedSecret})nonce←deriveBaseNonce(sharedSecret)

  4. plaintext←ChaCha20Poly1305.open(ephemeralKey,nonce,ciphertext)\text{plaintext} \leftarrow \text{ChaCha20Poly1305.open}(\text{ephemeralKey}, \text{nonce}, \text{ciphertext})plaintext←ChaCha20Poly1305.open(ephemeralKey,nonce,ciphertext)

  5. if plaintext=⊥\text{plaintext} = \botplaintext=⊥, reject the message

  6. otherwise, parse (secret,msg)(\text{secret}, \text{msg})(secret,msg) from plaintext\text{plaintext}plaintext

  7. expectedEncapsulatedSecret←secret×G\text{expectedEncapsulatedSecret} \leftarrow \text{secret} \times GexpectedEncapsulatedSecret←secret×G (extra check requiring the sender to prove that they know secret\text{secret}secret)

  8. if expectedEncapsulatedSecret≠encapsulatedSecret\text{expectedEncapsulatedSecret} \neq \text{encapsulatedSecret}expectedEncapsulatedSecret=encapsulatedSecret, reject the message

  9. otherwise, return msg\text{msg}msg

https://datatracker.ietf.org/doc/html/rfc5869