tesla

package
v0.2.0-rc.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 26, 2026 License: Apache-2.0 Imports: 8 Imported by: 0

Documentation

Overview

Package tesla implements a TESLA-inspired hash-chain key schedule for delayed disclosure of packet authentication keys.

Key Schedule Overview

Keys form a backward hash chain: the executor generates a random secret seed k_L (the tail) and hashes it L times to obtain the public anchor k_0:

k_0 = H^L(k_L)          (public anchor, published at setup)
k_i = H(k_{i+1})        (each key is the hash of the next one)

The executor uses key k_i during epoch i (1 ≤ i < L) and discloses it after a configurable disclosure delay d has elapsed. Because k_0 is public, epoch 0 has no signing key: nothing is tagged before epoch 1 starts. Because k_L is never disclosed, nothing is tagged from epoch L on either. A verifier who has buffered packets from epoch i can verify them once k_i is published by checking:

H^i(k_i) == k_0

Given a disclosed key k_τ, the key for an earlier epoch t (t ≤ τ) is reconstructed by hashing forward τ-t times:

k_t = H^(τ-t)(k_τ)

Per-Measurement Derivation

Because a single executor may service multiple measurements concurrently, an additional per-measurement key ak is derived from the current chain key:

ak = HKDF-SHA256(secret=k_i, info=measurement_id, length=32)

Authentication Tag

The authentication tag written into the IPv4 IPID field is:

tag = SipHash-2-4(k0 || k1 = ak[0:16], packet[0:64])[0:2]   (16-bit truncation)

with the packet's IPID and checksum fields zeroed. The eBPF tagger (tagger.c) and the pure-Go fallback compute it identically; see ComputeTag.

Index

Constants

View Source
const DefaultChainHorizon = 7 * 24 * time.Hour

DefaultChainHorizon is the uptime a default-sized chain covers. The default ChainLength is DefaultChainHorizon/Delay, so a longer epoch gives a shorter chain rather than a shorter usable lifetime.

Variables

This section is empty.

Functions

func ChainSeed

func ChainSeed(seed []byte, generation int64) ([]byte, error)

ChainSeed derives the tail k_L of one chain from a configured seed:

k_L = HKDF-SHA256(secret=seed, info="debuglet tesla chain <generation>", length=32)

Each generation of the same seed yields a different chain, so a restart with an unchanged seed does not reuse keys an earlier chain disclosed.

func ComputeTag

func ComputeTag(ak, payload []byte) (uint16, error)

ComputeTag computes the 16-bit attribution tag both taggers write into the IPv4 Identification field: SipHash-2-4 keyed with ak over at most the first 64 bytes of the packet, as tagger.c computes it in the kernel. It does not canonicalize the packet; the caller zeroes the mutable header fields.

func DeriveAK

func DeriveAK(k []byte, measurementID []byte) ([]byte, error)

DeriveAK computes the per-measurement authentication key:

ak = HKDF-SHA256(secret=k, info=measurementID, length=32)

measurementID should be the raw bytes of the UUID (or any stable byte representation of the measurement identifier).

func DeriveFromDisclosed

func DeriveFromDisclosed(disclosedKey []byte, disclosedEpoch, targetEpoch int64) ([]byte, error)

DeriveFromDisclosed reconstructs the key at targetEpoch given a disclosed key at disclosedEpoch. Because the chain runs backward (k_i = H(k_{i+1})), a verifier can only derive keys at epochs ≤ disclosedEpoch by hashing the disclosed key forward (disclosedEpoch → targetEpoch, t ≤ disclosedEpoch):

k_t = H^(disclosedEpoch - t)(k_disclosedEpoch)

To reconstruct a later (newer) key you would need an even newer disclosed key, since the chain cannot be inverted.

func VerifyChain

func VerifyChain(anchor, key []byte, t int64) bool

VerifyChain checks that H^t(k_t) == k_0 (the public anchor). A verifier calls this after reconstructing k_t to confirm its authenticity.

func VerifyTag

func VerifyTag(disclosedKey []byte, epoch int64, measurementID, packet []byte, tag uint16) (bool, error)

VerifyTag checks whether tag matches the expected tag for the packet at a given epoch, given the disclosed key for that epoch and the measurement ID. A packet that begins with an IPv4 header has its IPID and checksum fields zeroed before hashing, as both taggers do.

Types

type Config

type Config struct {
	// Seed is the secret tail k_L of the hash chain. If nil, a random
	// 32-byte seed is generated on the first call to NewKeySchedule.
	// The seed is NEVER disclosed; it is only used internally to derive
	// keys by hashing backwards toward k_0.
	Seed []byte

	// ChainLength L is the total number of epochs supported by this
	// schedule. The keys k_0 … k_L are generated; k_0 is the public
	// anchor and k_L is derived from the seed.
	//
	// If zero, it is derived from Delay so that the chain covers
	// DefaultChainHorizon of uptime. Sizing this from a wall-clock horizon
	// matters: k_L is never disclosed, so the last signing epoch is L-1 and
	// from the start of epoch L (Expiry) the schedule has no signing key and
	// nothing is tagged.
	ChainLength int64

	// Delay is the interval duration I (one epoch). A key disclosed after
	// the disclosure delay d (expressed as a number of epochs) has elapsed.
	// Defaults to 10 seconds.
	Delay time.Duration

	// Epoch is the reference wall-clock time that anchors epoch 0.
	// Defaults to the time NewKeySchedule is called.
	Epoch time.Time
}

Config carries the tuneable parameters of the TESLA key schedule.

type KeySchedule

type KeySchedule struct {
	// contains filtered or unexported fields
}

KeySchedule is a thread-safe TESLA hash-chain key schedule.

Keys are indexed by epoch number t where t ∈ [0, L]:

epoch t covers the time interval [Epoch + t*Delay, Epoch + (t+1)*Delay).

The chain direction is backward: k_0 is the public anchor and k_L is the private seed tail. Key k_t is used during epoch t and disclosed after the disclosure delay d has elapsed (i.e., once epoch t+d has started).

k_0 is public from setup, so it never signs: epoch 0, and any instant before Epoch, has no usable signing key. The first usable key is k_1 at Epoch+Delay and the last is k_{L-1}; from Expiry, the start of epoch L, no key signs.

func NewKeySchedule

func NewKeySchedule(cfg Config) (*KeySchedule, error)

NewKeySchedule creates a KeySchedule from cfg.

The chain direction is backward (k_i = H(k_{i+1})). The private seed k_L is stored internally and the public anchor k_0 = H^L(seed) is computed once.

If cfg.Seed is empty a cryptographically random 32-byte seed is generated. A seed that is not exactly 32 bytes long is folded through SHA-256 so that k_L is always one key wide. If cfg.Delay is zero it defaults to 10 seconds. If cfg.ChainLength is zero it is derived from Delay (see DefaultChainHorizon).

func (*KeySchedule) Anchor

func (ks *KeySchedule) Anchor() []byte

Anchor returns k_0, the public anchor that should be published at setup. Verifiers use it to check consistency: H^t(k_t) == k_0.

func (*KeySchedule) ChainLength

func (ks *KeySchedule) ChainLength() int64

ChainLength returns L, the highest epoch this schedule can serve.

func (*KeySchedule) ComputeTagForPacket

func (ks *KeySchedule) ComputeTagForPacket(t time.Time, measurementID, payload []byte) (uint16, error)

ComputeTagForPacket derives ak from the chain key at time t and then computes the tag over payload. It fails while no chain key is usable.

func (*KeySchedule) Config

func (ks *KeySchedule) Config() Config

Config returns a copy of the schedule's configuration.

func (*KeySchedule) CurrentKey

func (ks *KeySchedule) CurrentKey(t time.Time) []byte

CurrentKey returns the chain key k_t for the epoch that contains time t, or nil while no key is usable: epochOf maps epoch 0 and any instant before Epoch to 0, whose key is the public anchor (see KeySchedule), and every instant from the start of epoch L to L, whose key k_L is never disclosed. Every signing path reads the key here, so this is the one place the rule is decided.

func (*KeySchedule) DisclosedKey

func (ks *KeySchedule) DisclosedKey(t time.Time) (index int64, key []byte, ok bool)

DisclosedKey returns the epoch index and key that should be disclosed at time t. A key for epoch τ is disclosed once d disclosure-delay epochs have elapsed after τ. With d=1, the key for epoch (current−1) is disclosed when epoch current starts.

If t is still within epoch 0 (no key is disclosable yet), ok is false. From epoch L on the disclosed key stays k_{L-1}, the last signing key; k_L is never disclosed.

func (*KeySchedule) Exhausted

func (ks *KeySchedule) Exhausted(t time.Time) bool

Exhausted reports whether time t falls at or past the end of the chain, the start of epoch L. k_L is never disclosed, so an exhausted schedule has no signing key and CurrentKey returns nil from then on.

func (*KeySchedule) Expiry

func (ks *KeySchedule) Expiry() time.Time

Expiry returns the wall-clock time at which the chain runs out.

func (*KeySchedule) KeyAtEpoch

func (ks *KeySchedule) KeyAtEpoch(epoch int64) ([]byte, error)

KeyAtEpoch returns the chain key for the given epoch index.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL