npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

crypto-iff-core

v0.1.0

Published

Cryptographic challenge-response (IFF) protocol for drone fleet authentication using Ed25519, SHA-256 hash chains, and HKDF

Readme

crypto-iff-core

npm version license

Cryptographic challenge-response (IFF) protocol for drone fleet authentication using Ed25519 signatures, SHA-256 hash chains, and HKDF key derivation.

Overview

IFF (Identify Friend or Foe) is a real-time authentication protocol originally developed for military radar systems. This library implements a modern cryptographic variant designed for drone fleet management.

The problem: in a fleet of autonomous drones, a verifier (ground station or lead drone) needs to confirm that a responding drone is a legitimate member of the fleet — not a spoofed or compromised unit. This must happen quickly, with minimal bandwidth, and without exposing long-term secrets.

crypto-iff-core solves this with a three-layer approach:

  1. Ed25519 signatures — identity binding and message authentication
  2. SHA-256 hash chains — lightweight one-time proof of knowledge (no private key transmission per challenge)
  3. HKDF key derivation — deterministic per-drone, per-epoch chain seeds from a single fleet master secret

Features

  • Ed25519 keypair generation, signing, and verification
  • HKDF-SHA256 key derivation for per-drone, per-epoch chain seeds
  • SHA-256 hash chain generation and verification with configurable length
  • Challenge-response protocol with replay protection (timestamps + nonces)
  • Compact binary wire format (92-byte challenges, 124-byte responses)
  • Constant-time byte comparison to prevent timing attacks
  • Revocation list support
  • Zero runtime dependencies beyond tweetnacl and @noble/hashes
  • Pure ESM, tree-shakeable (sideEffects: false)

Installation

npm install crypto-iff-core

Quick Start

import {
  generateKeypair,
  deriveChainSeed,
  generateHashChain,
  createChallenge,
  verifyChallenge,
  createResponse,
  verifyResponse,
  randomBytes,
  toHex,
} from 'crypto-iff-core';

// --- Setup (done once per epoch) ---

// Fleet authority keypair (signs challenges)
const fleetAuth = generateKeypair();

// Verifier and drone keypairs
const verifier = generateKeypair();
const drone = generateKeypair();

// Derive a hash chain for this drone in epoch 1
const masterSeed = randomBytes(32);
const epoch = 1;
const chainSeed = deriveChainSeed(masterSeed, epoch, drone.publicKey);
const hashChain = generateHashChain(chainSeed, 100);

// Register the drone (verifier stores the anchor)
const droneRegistry = [
  {
    dronePub: drone.publicKey,
    callsign: 'ALPHA-1',
    anchor: hashChain.anchor,
    lastKnownHash: hashChain.anchor,
  },
];

// --- Protocol (per authentication) ---

// 1. Verifier creates a challenge
const challenge = createChallenge(verifier, epoch, fleetAuth.secretKey);

// 2. Drone verifies the challenge came from a legitimate fleet authority
const isValid = verifyChallenge(challenge, fleetAuth.publicKey);

// 3. Drone responds with the next hash chain value
let chainIndex = hashChain.length - 2; // start from tip-1, count down
const response = createResponse(challenge, drone, hashChain, chainIndex);

// 4. Verifier validates the response
const result = verifyResponse(response, challenge, droneRegistry, new Set());

console.log(result.status);   // 'FRIEND'
console.log(result.callsign); // 'ALPHA-1'

Core Concepts

Hash Chains

A hash chain is a sequence of values where each is the SHA-256 hash of the previous one:

seed → h₀ = SHA-256(seed) → h₁ = SHA-256(h₀) → ... → h_{n-1} (anchor)

The anchor (last value) is published. Values are revealed in reverse order — the drone reveals h_{n-2} first, then h_{n-3}, etc. The verifier checks that hashing the revealed value produces the last known hash. This gives each value one-time-use authenticity without transmitting private keys.

Epochs

Epochs are time periods (e.g., a mission or a day). Each epoch gets a fresh hash chain per drone, derived deterministically via HKDF from a shared master seed. When an epoch rotates, old chains are discarded.

Challenge-Response Flow

  Verifier                              Drone
     │                                    │
     │  1. createChallenge()              │
     │  ──────── Challenge (92B) ───────► │
     │           [verifierId, nonce,      │
     │            timestamp, epoch, sig]  │
     │                                    │  2. verifyChallenge()
     │                                    │  3. createResponse()
     │  ◄─────── Response (124B) ──────── │
     │           [nonce, hashValue,       │
     │            epoch, droneIdHint,     │
     │            timestamp, sig]         │
     │                                    │
     │  4. verifyResponse()               │
     │     → FRIEND / FOE / REVOKED       │
     │                                    │

API Reference

Keypair Operations

generateKeypair(): Ed25519Keypair

Generate a new Ed25519 keypair.

sign(message: Uint8Array, secretKey: Uint8Array): Uint8Array

Create a detached Ed25519 signature (64 bytes).

verify(message: Uint8Array, signature: Uint8Array, publicKey: Uint8Array): boolean

Verify a detached Ed25519 signature.

Key Derivation

deriveChainSeed(masterSeed: Uint8Array, epoch: number, dronePub: Uint8Array): Uint8Array

Derive a 32-byte per-drone, per-epoch chain seed using HKDF-SHA256.

  • masterSeed — 32-byte fleet master secret
  • epoch — epoch number (used as 4-byte big-endian salt)
  • dronePub — 32-byte drone public key (used in info string)

Hash Chain

generateHashChain(seed: Uint8Array, length: number): HashChain

Generate a hash chain of length values from a 32-byte seed. chain[0] = SHA-256(seed), each subsequent value is SHA-256(previous). The anchor is chain[length - 1].

verifyHashLink(hashValue: Uint8Array, expectedNext: Uint8Array): boolean

Check that SHA-256(hashValue) === expectedNext.

findChainDistance(hashValue: Uint8Array, expectedNext: Uint8Array, maxSteps: number): number

Walk the chain forward up to maxSteps hashes to find expectedNext. Returns the number of steps, or -1 if not reachable.

Protocol

createChallenge(verifierKeypair: Ed25519Keypair, epoch: number, fleetAuthKey: Uint8Array): Challenge

Create a signed challenge. The challenge includes a random 16-byte nonce, the current timestamp, the epoch, and a 4-byte verifier ID hint.

  • fleetAuthKey — the fleet authority's secret key (signs the challenge)

verifyChallenge(challenge: Challenge, fleetAuthPub: Uint8Array, toleranceSec?: number): boolean

Verify a challenge signature and check that the timestamp is within toleranceSec (default: 30) of the current time.

createResponse(challenge: Challenge, droneKeypair: Ed25519Keypair, hashChain: HashChain, chainIndex: number): Response

Create a signed response to a challenge. The response echoes the challenge nonce, reveals the hash chain value at chainIndex, and includes a 4-byte drone ID hint.

  • chainIndex must be >= 0 and < chain.length - 1 (the anchor is never revealed)

verifyResponse(response: Response, challenge: Challenge, droneRegistry: DroneEntry[], revocationList: RevocationList, maxChainGap?: number): VerificationResult

Verify a response against the challenge, drone registry, and revocation list. Returns a VerificationResult with status FRIEND, FOE, or REVOKED.

  • maxChainGap — maximum hash chain steps to search (default: 10). Limits CPU cost of chain walking.

Serialization

serializeChallenge(challenge: Challenge): Uint8Array

Serialize a challenge to a 92-byte buffer.

deserializeChallenge(data: Uint8Array): Challenge

Deserialize a 92-byte buffer into a Challenge. Throws if length is wrong.

serializeResponse(response: Response): Uint8Array

Serialize a response to a 124-byte buffer.

deserializeResponse(data: Uint8Array): Response

Deserialize a 124-byte buffer into a Response. Throws if length is wrong.

Utilities

toHex(bytes: Uint8Array): string

Convert bytes to a lowercase hex string.

fromHex(hex: string): Uint8Array

Convert a hex string to bytes.

concat(...arrays: Uint8Array[]): Uint8Array

Concatenate multiple Uint8Array values.

equal(a: Uint8Array, b: Uint8Array): boolean

Constant-time byte array comparison. Returns false if lengths differ.

randomBytes(length: number): Uint8Array

Generate cryptographically random bytes (delegates to tweetnacl).

nowSeconds(): number

Current time as Unix seconds (floored).

Types

Ed25519Keypair

| Field | Type | Size | |---|---|---| | publicKey | Uint8Array | 32 bytes | | secretKey | Uint8Array | 64 bytes |

HashChain

| Field | Type | Description | |---|---|---| | seed | Uint8Array | 32-byte HKDF-derived seed | | chain | Uint8Array[] | [h₀, h₁, ..., h_{n-1}] | | anchor | Uint8Array | chain[length - 1], the published tip | | length | number | Number of chain values |

Challenge

| Field | Type | Size | |---|---|---| | verifierId | Uint8Array | 4 bytes | | nonce | Uint8Array | 16 bytes | | timestamp | number | uint32 (Unix seconds) | | epoch | number | uint32 | | signature | Uint8Array | 64 bytes |

Response

| Field | Type | Size | |---|---|---| | nonce | Uint8Array | 16 bytes | | hashValue | Uint8Array | 32 bytes | | epoch | number | uint32 | | droneIdHint | Uint8Array | 4 bytes | | timestamp | number | uint32 | | signature | Uint8Array | 64 bytes |

VerificationResult

| Field | Type | Description | |---|---|---| | status | 'FRIEND' \| 'FOE' \| 'REVOKED' | Authentication result | | droneIdHint | string | Hex string of the 4-byte hint | | callsign | string? | Resolved callsign if in registry | | timestamp | number | When verification was performed |

DroneEntry

| Field | Type | Description | |---|---|---| | dronePub | Uint8Array | 32-byte full public key | | callsign | string | Human-readable identifier | | anchor | Uint8Array | 32-byte published chain tip | | lastKnownHash | Uint8Array | Advances on each FRIEND verification |

RevocationList

type RevocationList = Set<string>; // hex-encoded 32-byte public keys

Protocol Flow

Step-by-step walkthrough of the full challenge-response cycle:

  1. Setup — The fleet authority generates a master seed. For each drone and epoch, deriveChainSeed() produces a deterministic 32-byte seed. generateHashChain() builds the chain; the anchor is distributed to all verifiers.

  2. Challenge — The verifier calls createChallenge(), which generates a random nonce, records the current timestamp and epoch, and signs the payload with the fleet authority's secret key. The 92-byte challenge is sent to the drone.

  3. Validation — The drone calls verifyChallenge() to confirm the signature is valid and the timestamp is fresh (within tolerance). This proves the challenge came from a legitimate fleet authority.

  4. Response — The drone calls createResponse(), revealing the next unused hash chain value and signing the response with its own secret key. The 124-byte response is sent back.

  5. Verification — The verifier calls verifyResponse(), which:

    • Checks the nonce and epoch match the original challenge
    • Looks up the drone in the registry by its 4-byte ID hint
    • Checks the revocation list
    • Walks the hash chain forward from the revealed value to lastKnownHash (up to maxChainGap steps)
    • Verifies the Ed25519 signature against the drone's public key
    • On success, advances lastKnownHash and returns FRIEND

Wire Format

Challenge (92 bytes)

| Offset | Size | Field | |---|---|---| | 0 | 4 | verifierId | | 4 | 16 | nonce | | 20 | 4 | timestamp (uint32 BE) | | 24 | 4 | epoch (uint32 BE) | | 28 | 64 | signature |

Response (124 bytes)

| Offset | Size | Field | |---|---|---| | 0 | 16 | nonce | | 16 | 32 | hashValue | | 48 | 4 | epoch (uint32 BE) | | 52 | 4 | droneIdHint | | 56 | 4 | timestamp (uint32 BE) | | 60 | 64 | signature |

All multi-byte integers are big-endian. No padding or alignment.

Security Considerations

  • Ed25519 via tweetnacl — well-audited, pure-JS implementation of Curve25519/Ed25519
  • SHA-256 and HKDF via @noble/hashes — audited, high-performance pure-JS cryptographic hashes by Paul Miller
  • Constant-time comparisonequal() uses bitwise OR accumulation to avoid timing side-channels
  • Replay protection — challenges include a random 16-byte nonce and a timestamp checked within a configurable tolerance window (default: 30 seconds)
  • Hash chain gap limitmaxChainGap (default: 10) bounds the CPU cost of chain verification and prevents an attacker from replaying old chain values
  • One-time hash values — each chain value is used once and lastKnownHash advances forward, preventing reuse
  • No long-term secrets on the wire — only hash chain values and signatures are transmitted; private keys never leave their host

Dependencies

| Package | Purpose | |---|---| | tweetnacl | Ed25519 signatures and random bytes | | @noble/hashes | SHA-256, HKDF-SHA256 |

Both are pure JavaScript with no native dependencies.

Development

git clone https://github.com/tsolman/cryptoiff.git
cd cryptoiff/crypto-iff-core
npm install
npm run build
npm test

License

MIT