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
  1. Protocol Details

Keys & Key Derivation

PreviousAlgebraic Primitives & NotationNextStealth Addresses

Last updated 1 year ago

Key Derivation

The user's , which we will refer to as sk\text{sk}sk, is a uniformly-sampled 32-byte string.

In Nocturne's MetaMask Snap, we derive the sk \text{sk}sk using the snap_getBip44Entropy method at derivation path m / 44' / 6789'.

Let GGGdenote the generator of Baby Jubjub's prime-order subgroup. The user's spending public key, which we will refer to as PK\text{PK}PK, is an element of Baby Jubjub defined as PK=SHA512(sk)[0:32]×G\text{PK} = \text{SHA512}(\text{sk})[0:32] \times GPK=SHA512(sk)[0:32]×G ([0:32][0:32][0:32]means "0th through 32nd byte"). This is only used to verify signatures in-circuit. It never appears on-chain or leaves the client.

What we refer to as the "generator" is often called the "base point" in order to differentiate between generator of Baby Jubjub's curve group and the generator of Baby Jubjub's prime-order subgroup. Since all operations are performed in the prime-order subgroup, we're ignoring this distinction and using the word "generator" to refer to the generator of the prime-order subgroup.

The user's is an element of Fr\mathbb{F}_rFr​ defined as vk=H(PK.X ∣∣ PK.Y ∣∣ vkNonce)vk = H(\text{PK.X}\ ||\ \text{PK.Y}\ ||\ \text{vkNonce})vk=H(PK.X ∣∣ PK.Y ∣∣ vkNonce), where PK.X,PK.Y∈Fp\text{PK.X}, \text{PK.Y} \in \mathbb{F}_pPK.X,PK.Y∈Fp​are the x and y coordinates of PK\text{PK}PK respectively, vkNonce∈Fp\text{vkNonce} \in \mathbb{F}_pvkNonce∈Fp​, and vkNonce\text{vkNonce}vkNonce must be chosen such that the output of the hash is an element of Fr\mathbb{F}_rFr​.

That last provision is needed because HHH returns an element of Fp\mathbb{F}_pFp​, but we need an element of Fr\mathbb{F}_rFr​. A reduction modulo rrr would bias the key generation, and using Poseidon over Fr\mathbb{F}_rFr​ would be prohibitively expensive in-circuit. But this approach suffers from neither issue - during key generation, we can increment vkNonce and try again if the output of the hash is not an element of Fr\mathbb{F}_rFr​.

In theory, rejection sampling like this comes small performance cost. ~91% of the possible vkNonce>r\text{vkNonce} > rvkNonce>r , so we expect that, on average, it will take 10-11 tries to find a "good" nonce. In practice, the cost is negligible - 11 attempts takes ~30ms with a very naive implementation.

spending key
viewing key