Epoch Signing & Merkle Trees

Overview

PhoenixSig does not use long-lived signing keys. Instead, it derives a fresh key pair for each epoch and commits all epoch public keys to a Merkle tree. This document explains how epoch-based signing works and how Merkle trees enable efficient verification.

Why Ephemeral Keys?

Long-lived private keys create a single point of catastrophic failure: if the key is compromised, all past and future signatures are affected. Ephemeral keys bound the damage:

  • Each key is used for a limited number of operations within its epoch
  • After the epoch, the key is destroyed
  • Compromising one epoch key does not affect any other epoch

Combined with DyLWE forward secrecy, this means even capturing the current epoch key reveals nothing about past or future keys.

Epoch Key Derivation

Each epoch key pair is deterministically derived from the current system state and the device's VaultKey. The derivation feeds into the standard PQC key generation for ML-DSA-65 or SLH-DSA. This ensures that:

  • The key pair is fully determined by the inputs (auditable, testable)
  • The VaultKey is included (Phoenix Injection Rule)
  • The resulting keys are valid PQC keys with full quantum resistance

Signing Flow

  1. Determine current epoch (check if epoch transition is needed based on time/count policy)
  2. Derive epoch key pair from current state and VaultKey
  3. Sign the message using the PQC engine with the ephemeral key
  4. Package the signature bundle with the epoch public key and Merkle authentication path
  5. Securely erase the ephemeral signing key from memory
  6. Advance the internal state via DyLWE evolution

The Merkle Tree

The Problem

If each epoch uses a different public key, how does a verifier know which key to trust? Pre-sharing every epoch key is impractical — there could be thousands of epochs over a device’s lifetime.

The Solution

During initialization, PhoenixSig pre-computes a batch of epoch public keys and arranges them as leaf nodes in a Merkle hash tree. The tree’s root hash — the RootPK — is the device’s public identity.

[RootPK] / \ [H_01] [H_23] / \ / \ [pk_0] [pk_1] [pk_2] [pk_3] ... (epoch public keys)

Verification

Each signature includes:

  • pk_epoch: the public key used for this epoch
  • merkle_path: the sibling hashes needed to reconstruct the path from pk_epoch to RootPK

The verifier performs two checks:

  1. Merkle verification: Recompute the root from pk_epoch and merkle_path. If it matches RootPK, the key is legitimate.
  2. Signature verification: PQC.Verify(pk_epoch, message, sig). If it passes, the signature is authentic.

Both must pass for the signature to be accepted.

Tree Parameters

ParameterDescriptionTypical Value
Tree depthNumber of levels (determines max epochs)20 (1M epochs)
Hash functionUsed for internal nodesSHA-3-256
Auth path sizeSibling hashes per signature20 × 32 = 640 bytes

Epoch Exhaustion and Tree Renewal

When all leaves in the Merkle tree have been used, a new tree must be created. This involves:

  1. Generating a new batch of epoch keys from the current state
  2. Building a new Merkle tree
  3. Publishing the new RootPK

The old RootPK can be chained to the new one by having the last signature under the old tree endorse the new RootPK, creating a verifiable key lineage.


← Back to Documentation Request Demo