=============== LIBRARY RULES =============== From library maintainers: - Tokens rotate on a configurable time window (default 30 seconds, like TOTP). - Output is a sequence of common English words — designed to be spoken aloud over a phone call or in person. - HMAC-based — the shared secret never leaves the device. ### Setup the project environment Source: https://github.com/forgesworn/spoken-token/blob/main/CONTRIBUTING.md Clone the repository and install dependencies to begin development. ```bash git clone https://github.com/forgesworn/spoken-token.git cd spoken-token npm install ``` -------------------------------- ### Run Example Scripts Source: https://github.com/forgesworn/spoken-token/blob/main/README.md Execute example scripts for various use cases like rideshare, phone authentication, and identity verification. ```bash npx tsx examples/rideshare.ts ``` ```bash npx tsx examples/phone-auth.ts ``` ```bash npx tsx examples/identity-verify.ts ``` -------------------------------- ### Install spoken-token Source: https://github.com/forgesworn/spoken-token/blob/main/llms.txt Install the spoken-token library using npm. ```bash npm install spoken-token ``` -------------------------------- ### Common Patterns: Group Entry with Per-Member Tokens Source: https://github.com/forgesworn/spoken-token/blob/main/llms-full.txt Example of using spoken-token for group entry where each member has a personal token. ```APIDOC ## Group Entry with Per-Member Tokens ### Description This pattern illustrates how to use `spoken-token` for group entry scenarios where each member derives their own personal token. The system can then verify any member's token against a list of known members. ### Usage 1. **System**: Define the list of members and set up a counter. 2. **Each Member's Device**: Derive a personal token using the shared secret, a group identifier, the counter, and their unique member identifier. 3. **Entry System**: Verify the spoken word against the group identifier, counter, and the list of known members. The system can identify which member the token belongs to. ### Key Functions - `deriveToken` - `verifyToken` - `getCounter` ### Request Example ```typescript import { deriveToken, verifyToken, getCounter } from 'spoken-token' const members = ['alice@example.com', 'bob@example.com', 'carol@example.com'] const counter = getCounter(Date.now() / 1000) // Each member derives their personal token on their device: const aliceToken = deriveToken(sharedSecret, 'event:2024-conf', counter, undefined, 'alice@example.com') // Door system verifies whoever shows up: const result = verifyToken(sharedSecret, 'event:2024-conf', counter, spokenWord, members, { tolerance: 1, encoding: { format: 'words', count: 2 } }) // → { status: 'valid', identity: 'alice@example.com' } ``` ``` -------------------------------- ### Derive and Verify Rideshare Token Source: https://context7.com/forgesworn/spoken-token/llms.txt Example demonstrating how to derive a spoken token for rideshare verification and how to verify it, including handling clock skew. Imports: deriveToken, verifyToken, getCounter, randomSeed. ```typescript import { deriveToken, verifyToken, getCounter, randomSeed } from 'spoken-token' // Server generates shared secret during booking, stores keyed by ride ID const sharedSecret = randomSeed(32) // Both rider and driver compute same counter from wall-clock time const counter = getCounter(Date.now() / 1000) // 7-day rotation // --- Rider's phone displays --- const riderWord = deriveToken(sharedSecret, 'rideshare:pickup', counter) console.log(`Your verification word: "${riderWord}"`) // --- Driver's app verifies --- const spokenWord = riderWord // What driver heard rider say const result = verifyToken( sharedSecret, 'rideshare:pickup', counter, spokenWord, undefined, { tolerance: 1 } // Handle clock skew during ride ) if (result.status === 'valid') { console.log('✓ Rider verified — start trip') } else { console.log('✗ Verification failed — do not start trip') } ``` -------------------------------- ### Common Patterns: Rideshare Pickup Source: https://github.com/forgesworn/spoken-token/blob/main/llms-full.txt Example of using spoken-token for rideshare pickup authentication. ```APIDOC ## Rideshare Pickup Authentication ### Description This pattern demonstrates how to use `spoken-token` to generate and verify tokens for rideshare pickups, allowing for a small tolerance to account for counter rotation during the ride. ### Usage 1. **Server-side**: Generate and store a shared secret keyed by ride ID. 2. **Rider's Device**: Derive a token using the shared secret, a unique identifier for the ride, and the current counter. 3. **Driver's App**: Verify the spoken word provided by the rider against the shared secret, ride identifier, and counter, with a specified tolerance. ### Key Functions - `deriveToken` - `verifyToken` - `getCounter` - `randomSeed` ### Request Example ```typescript import { deriveToken, verifyToken, getCounter } from 'spoken-token' // App generates shared secret and stores it server-side, keyed by ride ID const sharedSecret = randomSeed(32) const counter = getCounter(Date.now() / 1000) // 7-day window // Rider's phone shows: const riderWord = deriveToken(sharedSecret, 'rideshare:rider', counter) // Driver's app verifies what rider says: const result = verifyToken(sharedSecret, 'rideshare:rider', counter, spokenWord, undefined, { tolerance: 1 }) // tolerance: 1 absorbs counter rotation happening during the ride ``` ``` -------------------------------- ### Common Patterns: Phone Call Authentication Source: https://github.com/forgesworn/spoken-token/blob/main/llms-full.txt Example of using spoken-token for phone call authentication with directional tokens. ```APIDOC ## Phone Call Authentication ### Description This pattern uses `spoken-token` to generate directional verification words for phone calls, where neither party can derive the other's word. The counter rotation is set to match the call duration (e.g., 30 seconds). ### Usage 1. **System**: Derive two directional tokens (e.g., 'caller' and 'agent') using the shared secret, a unique identifier for the call, and a short-duration counter. 2. **IVR**: Presents the 'caller' token to the caller. 3. **Agent's System**: Displays the 'agent' token and the expected 'caller' token. ### Key Functions - `deriveDirectionalPair` - `getCounter` ### Request Example ```typescript import { deriveDirectionalPair, getCounter } from 'spoken-token' // 30-second rotation to match call duration const counter = getCounter(Date.now() / 1000, 30) const { caller, agent } = deriveDirectionalPair( sharedSecret, 'support-call', ['caller', 'agent'], counter ) // IVR says to caller: "Your verification word is: timber" // Agent's system shows: "Caller should say: timber. Your response word is: canyon" // Neither party can derive the other's word. ``` ``` -------------------------------- ### Derive Directional Pair for Phone Authentication Source: https://context7.com/forgesworn/spoken-token/llms.txt Example for deriving directional tokens for phone authentication, ensuring neither party can derive the other's token without the shared secret. Imports: deriveDirectionalPair, getCounter, randomSeed. ```typescript import { deriveDirectionalPair, getCounter, randomSeed } from 'spoken-token' // Shared secret established during account setup const sharedSecret = randomSeed(32) // 30-second rotation for real-time phone calls const counter = getCounter(Date.now() / 1000, 30) // Both sides derive the same pair const { caller, agent } = deriveDirectionalPair( sharedSecret, 'support-call', ['caller', 'agent'], counter ) // IVR system tells caller: "Your verification word is: timber" console.log(`Caller's word: "${caller}"`) // Agent's screen shows: "Caller should say: timber" // Agent's screen shows: "Your response word is: canyon" console.log(`Agent's word: "${agent}"`) // Neither party can derive the other's word without the secret // Prevents "echo attack" where second speaker parrots the first ``` -------------------------------- ### Run Build, Test, and Lint Commands Source: https://github.com/forgesworn/spoken-token/blob/main/GEMINI.md Execute these npm scripts to build the project, run tests, check types, and lint the code. The test command includes watch mode for continuous testing. ```bash npm run build npm test npm run test:watch npm run typecheck npm run lint npm run lint:fix ``` ```bash npm test && npm run typecheck && npm run lint ``` -------------------------------- ### Run project tests Source: https://github.com/forgesworn/spoken-token/blob/main/CONTRIBUTING.md Execute the test suite using vitest in either single-run or watch mode. ```bash npm test # run once npm run test:watch # watch mode ``` -------------------------------- ### Run all project checks Source: https://github.com/forgesworn/spoken-token/blob/main/AGENTS.md Execute all tests, type checking, and linting before submitting changes. Ensures code quality and correctness. ```bash npm test && npm run typecheck && npm run lint ``` -------------------------------- ### deriveDirectionalPair Source: https://context7.com/forgesworn/spoken-token/llms.txt Derives two distinct tokens from one secret, one for each role, to prevent the 'echo problem'. Each role gets a cryptographically independent token. ```APIDOC ## deriveDirectionalPair ### Description Derives two distinct tokens from one secret—one per role. This prevents the "echo problem" where one party could parrot the other's token. Each role gets a cryptographically independent token that cannot be derived from the other without the shared secret. ### Method `deriveDirectionalPair` ### Parameters #### Path Parameters None #### Query Parameters None #### Request Body None ### Request Example ```typescript import { deriveDirectionalPair, getCounter, randomSeed } from 'spoken-token' const secret = randomSeed(32) // 30-second rotation for real-time phone calls const counter = getCounter(Date.now() / 1000, 30) // Derive a pair of tokens for caller and agent const pair = deriveDirectionalPair(secret, 'support-call', ['caller', 'agent'], counter) console.log(`Caller says: "${pair.caller}"`) // → 'timber' console.log(`Agent says: "${pair.agent}"`) // → 'canyon' // Neither party can derive the other's word without the secret // Words are always distinct — caller cannot parrot the agent's word // With custom encoding (2-word phrases) const securePair = deriveDirectionalPair( secret, 'bank-verification', ['customer', 'banker'], counter, { format: 'words', count: 2 } ) console.log(`Customer: "${securePair.customer}"`) // → 'carbon timber' console.log(`Banker: "${securePair.banker}"`) // → 'sunset glacier' ``` ### Response #### Success Response (200) - **pair** (object) - An object containing tokens for each specified role. - **role1_token** (string) - Token for the first role. - **role2_token** (string) - Token for the second role. #### Response Example ```json { "caller": "timber", "agent": "canyon" } ``` ``` -------------------------------- ### Define encoding options Source: https://github.com/forgesworn/spoken-token/blob/main/README.md Configuration objects for token output formats. ```typescript { format: 'words', count?: number, wordlist?: string[] } // default: 1 word { format: 'pin', digits?: number } // default: 4 digits { format: 'hex', length?: number } // default: 8 chars ``` -------------------------------- ### Use a custom wordlist Source: https://github.com/forgesworn/spoken-token/blob/main/README.md Overrides the default wordlist with a custom array of 2048 entries. ```typescript deriveToken(secret, context, counter, { format: 'words', wordlist: myWordlist }) ``` -------------------------------- ### Constants Source: https://github.com/forgesworn/spoken-token/blob/main/llms-full.txt Configuration constants for the spoken-token library. ```APIDOC ## `MAX_TOLERANCE` ### Description Maximum allowed tolerance value for `verifyToken`. ### Value `10` ``` ```APIDOC ## `MAX_COUNTER_OFFSET` ### Description Maximum allowed usage counter offset above the time-based counter. ### Value `100` ``` ```APIDOC ## `DEFAULT_ROTATION_INTERVAL` ### Description Default interval for token rotation in seconds (7 days). ### Value `604800` ``` ```APIDOC ## `DEFAULT_ENCODING` ### Description Default encoding format for tokens. ### Value `{ format: 'words', count: 1 }` ``` -------------------------------- ### Derive and Verify Tokens for Rideshare Pickup Source: https://github.com/forgesworn/spoken-token/blob/main/llms-full.txt Demonstrates generating a token for a rider and verifying it on the driver's app. Includes handling counter rotation with a tolerance. ```typescript import { deriveToken, verifyToken, getCounter } from 'spoken-token' // App generates shared secret and stores it server-side, keyed by ride ID const sharedSecret = randomSeed(32) const counter = getCounter(Date.now() / 1000) // 7-day window // Rider's phone shows: const riderWord = deriveToken(sharedSecret, 'rideshare:rider', counter) // Driver's app verifies what rider says: const result = verifyToken(sharedSecret, 'rideshare:rider', counter, spokenWord, undefined, { tolerance: 1 }) // tolerance: 1 absorbs counter rotation happening during the ride ``` -------------------------------- ### Derive and Verify Tokens for Group Entry Source: https://github.com/forgesworn/spoken-token/blob/main/llms-full.txt Shows how each member of a group can derive a personal token and how a system can verify any member's token. Supports specifying allowed members and encoding format. ```typescript import { deriveToken, verifyToken, getCounter } from 'spoken-token' const members = ['alice@example.com', 'bob@example.com', 'carol@example.com'] const counter = getCounter(Date.now() / 1000) // Each member derives their personal token on their device: const aliceToken = deriveToken(sharedSecret, 'event:2024-conf', counter, undefined, 'alice@example.com') // Door system verifies whoever shows up: const result = verifyToken(sharedSecret, 'event:2024-conf', counter, spokenWord, members, { tolerance: 1, encoding: { format: 'words', count: 2 } }) // → { status: 'valid', identity: 'alice@example.com' } ``` -------------------------------- ### Wordlist Utilities Source: https://github.com/forgesworn/spoken-token/blob/main/llms-full.txt Functions and constants related to the default English wordlist. ```APIDOC ## `WORDLIST: readonly string[]` ### Description The full 2048-word `en-v1` English wordlist. Curated for spoken clarity. ### Type `readonly string[]` ``` ```APIDOC ## `WORDLIST_SIZE: number` ### Description The number of words in the `WORDLIST`. ### Value `2048` ``` ```APIDOC ## `getWord(index)` ### Description Retrieves a word from the `WORDLIST` by its index. ### Method `getWord` ### Parameters - **index** (number) - The index of the word to retrieve (0–2047). ### Response - **string** - The word at the specified index. ``` ```APIDOC ## `indexOf(word)` ### Description Finds the index of a given word within the `WORDLIST`. ### Method `indexOf` ### Parameters - **word** (string) - The word to find the index of. ### Response - **number** - The index of the word, or -1 if the word is not found in the wordlist. ``` -------------------------------- ### Access and Query Wordlist Source: https://context7.com/forgesworn/spoken-token/llms.txt Access the built-in 2048-word English wordlist and retrieve words by index or find the index of a given word. Imports: WORDLIST, WORDLIST_SIZE, getWord, indexOf. ```typescript import { WORDLIST, WORDLIST_SIZE, getWord, indexOf } from 'spoken-token' // Full wordlist array console.log(WORDLIST.length) // → 2048 console.log(WORDLIST_SIZE) // → 2048 // Get word by index (0-2047) const word = getWord(0) // → first word in list const word42 = getWord(42) // → 43rd word in list // Find index of a word (-1 if not found) const index = indexOf('carbon') // → index of 'carbon' in wordlist const notFound = indexOf('xyz') // → -1 ``` -------------------------------- ### Derive Token with Different Secret Source: https://github.com/forgesworn/spoken-token/blob/main/PROTOCOL.md Derive a token using a different secret key, demonstrating that the secret is a primary factor in token generation. ```plaintext deriveToken | SECRET_2 | test | 0 | 1 word | carpet ``` -------------------------------- ### Base64 Encoding and Decoding Source: https://context7.com/forgesworn/spoken-token/llms.txt Convert byte arrays to Base64 strings and vice versa. Imports: bytesToBase64, base64ToBytes. ```typescript import { bytesToBase64, base64ToBytes } from 'spoken-token' // Base64 encoding/decoding const b64 = bytesToBase64(secret) // → 'ChtSPT...' const decoded = base64ToBytes(b64) // → Uint8Array ``` -------------------------------- ### Library Constants Source: https://context7.com/forgesworn/spoken-token/llms.txt Access configuration constants and limits used within the library. Imports: MAX_TOLERANCE, MAX_COUNTER_OFFSET, DEFAULT_ROTATION_INTERVAL, DEFAULT_ENCODING. ```typescript import { MAX_TOLERANCE, MAX_COUNTER_OFFSET, DEFAULT_ROTATION_INTERVAL, DEFAULT_ENCODING } from 'spoken-token' console.log(MAX_TOLERANCE) // → 10 (max tolerance for verifyToken) console.log(MAX_COUNTER_OFFSET) // → 100 (max usage counter offset) console.log(DEFAULT_ROTATION_INTERVAL) // → 604800 (7 days in seconds) console.log(DEFAULT_ENCODING) // → { format: 'words', count: 1 } ``` -------------------------------- ### verifyToken Source: https://github.com/forgesworn/spoken-token/blob/main/llms.txt Verifies a spoken token against a shared secret with support for tolerance windows. ```APIDOC ## verifyToken(secret, context, counter, input, identities?, options?) ### Description Verifies a spoken token with a tolerance window and optional per-identity matching. ### Parameters - **secret** (string) - Required - The shared secret. - **context** (string) - Required - The domain-separation string. - **counter** (number) - Required - The current time-based counter. - **input** (string) - Required - The token provided by the user. - **identities** (array) - Optional - List of valid identities. - **options** (object) - Optional - Verification options including tolerance window. ``` -------------------------------- ### Token Encoding and Decoding Source: https://github.com/forgesworn/spoken-token/blob/main/llms-full.txt Functions for encoding byte arrays into human-readable tokens and vice-versa. ```APIDOC ## `encodeAsHex(bytes, length?)` ### Description Encodes a byte array into a hexadecimal string. ### Method `encodeAsHex` ### Parameters - **bytes** (Uint8Array) - The byte array to encode. - **length** (number, optional) - The desired length of the output hex string. ### Response - **string** - The hexadecimal representation of the byte array. ### Request Example ```typescript import { encodeAsHex } from 'spoken-token' encodeAsHex(someBytes, 8) // → 'a3f2e1b0' ``` ``` ```APIDOC ## `encodeToken(bytes, encoding?)` ### Description Encodes a byte array into a spoken token using a specified encoding. ### Method `encodeToken` ### Parameters - **bytes** (Uint8Array) - The byte array to encode. - **encoding** (TokenEncoding union type, optional) - The encoding format to use. Words are space-joined. ### Response - **string** - The encoded spoken token. ``` -------------------------------- ### Verify Token with Zero Tolerance Source: https://github.com/forgesworn/spoken-token/blob/main/PROTOCOL.md Verify a token against its secret, context, and counter with zero tolerance. This requires an exact match. ```plaintext verifyToken | jazz | SECRET_1 | test | 0 | — | 0 | valid ``` -------------------------------- ### verifyToken Source: https://context7.com/forgesworn/spoken-token/llms.txt Verifies a spoken or entered token against a shared secret using constant-time comparison. Supports tolerance windows for clock skew and per-identity matching. ```APIDOC ## verifyToken ### Description Verifies a spoken or entered token against a shared secret using constant-time comparison. Supports tolerance windows for clock skew and per-identity matching to identify which member spoke. ### Parameters - **secret** (string) - Required - The shared secret used for verification. - **context** (string) - Required - The context string for the token. - **counter** (number) - Required - The counter value to verify against. - **token** (string) - Required - The token to verify. - **members** (string[]) - Optional - List of identities to match against. - **options** (object) - Optional - Configuration object including tolerance and encoding settings. ``` -------------------------------- ### Cryptographic Primitives Source: https://github.com/forgesworn/spoken-token/blob/main/llms-full.txt Pure JavaScript cryptographic utilities for use without external dependencies. ```APIDOC ## `sha256(data)` ### Description Computes the SHA-256 hash of the given data. ### Method `sha256` ### Parameters - **data** (Uint8Array) - The input data to hash. ### Response - **Uint8Array** - The SHA-256 hash of the data. ``` ```APIDOC ## `hmacSha256(key, data)` ### Description Computes the HMAC-SHA256 of the given data with the specified key. ### Method `hmacSha256` ### Parameters - **key** (Uint8Array) - The secret key. - **data** (Uint8Array) - The data to hash. ### Response - **Uint8Array** - The HMAC-SHA256 hash. ``` ```APIDOC ## `randomSeed(byteLength)` ### Description Generates a cryptographically secure random byte array of the specified length. ### Method `randomSeed` ### Parameters - **byteLength** (number) - The desired length of the random byte array in bytes. ### Response - **Uint8Array** - A random byte array. ``` ```APIDOC ## `hexToBytes(hex)` ### Description Converts a hexadecimal string to a byte array. ### Method `hexToBytes` ### Parameters - **hex** (string) - The hexadecimal string to convert. ### Response - **Uint8Array** - The resulting byte array. ``` ```APIDOC ## `bytesToHex(bytes)` ### Description Converts a byte array to its hexadecimal string representation. ### Method `bytesToHex` ### Parameters - **bytes** (Uint8Array) - The byte array to convert. ### Response - **string** - The hexadecimal string. ``` ```APIDOC ## `readUint16BE(bytes, offset)` ### Description Reads a 16-bit unsigned integer from a byte array in big-endian format at the specified offset. ### Method `readUint16BE` ### Parameters - **bytes** (Uint8Array) - The byte array to read from. - **offset** (number) - The offset within the byte array to start reading. ### Response - **number** - The 16-bit unsigned integer. ``` ```APIDOC ## `concatBytes(...arrays)` ### Description Concatenates multiple byte arrays into a single byte array. ### Method `concatBytes` ### Parameters - **arrays** (Uint8Array[]) - A variable number of byte arrays to concatenate. ### Response - **Uint8Array** - The concatenated byte array. ``` ```APIDOC ## `bytesToBase64(bytes)` ### Description Converts a byte array to its Base64 string representation. ### Method `bytesToBase64` ### Parameters - **bytes** (Uint8Array) - The byte array to convert. ### Response - **string** - The Base64 encoded string. ``` ```APIDOC ## `base64ToBytes(base64)` ### Description Converts a Base64 string to a byte array. ### Method `base64ToBytes` ### Parameters - **base64** (string) - The Base64 string to convert. ### Response - **Uint8Array** - The resulting byte array. ``` ```APIDOC ## `timingSafeEqual(a, b)` ### Description Compares two byte arrays for equality in a way that resists timing attacks. ### Method `timingSafeEqual` ### Parameters - **a** (Uint8Array) - The first byte array. - **b** (Uint8Array) - The second byte array. ### Response - **boolean** - True if the byte arrays are equal, false otherwise. ``` ```APIDOC ## `timingSafeStringEqual(a, b)` ### Description Compares two strings for equality in a way that resists timing attacks. ### Method `timingSafeStringEqual` ### Parameters - **a** (string) - The first string. - **b** (string) - The second string. ### Response - **boolean** - True if the strings are equal, false otherwise. ``` -------------------------------- ### Byte Array Utilities Source: https://context7.com/forgesworn/spoken-token/llms.txt Utilities for manipulating byte arrays, including concatenation and reading integers. Imports: concatBytes, readUint16BE. ```typescript import { concatBytes, readUint16BE } from 'spoken-token' // Byte utilities const combined = concatBytes(secret, data) const uint16 = readUint16BE(secret, 0) // Read 2-byte big-endian integer ``` -------------------------------- ### HMAC-SHA256 for Per-Member Tokens Source: https://github.com/forgesworn/spoken-token/blob/main/PROTOCOL.md Derives a 32-byte token using HMAC-SHA256, including an identity for unique per-member tokens. A null byte separates context from identity to prevent ambiguity. ```pseudocode token_bytes = HMAC-SHA256(secret, utf8(context) || 0x00 || utf8(identity) || counter_be32) ``` -------------------------------- ### verifyToken Source: https://github.com/forgesworn/spoken-token/blob/main/llms-full.txt Verifies a spoken or entered token against a shared secret using constant-time comparison. ```APIDOC ## verifyToken(secret, context, counter, input, identities?, options?) ### Description Verify a spoken or entered token against a shared secret. Uses constant-time comparison to prevent timing side-channels. ### Parameters #### Arguments - **secret** (Uint8Array | string) - Required - Shared secret - **context** (string) - Required - Context string - **counter** (number) - Required - Current time-based counter - **input** (string) - Required - The spoken or entered token to verify - **identities** (string[]?) - Optional - Array of member identities (max 100) - **options** (VerifyOptions?) - Optional - Encoding and tolerance settings ### Response #### Success Response - **status** ('valid' | 'invalid') - Verification status - **identity** (string?) - The matching identity (when identities array was provided) ``` -------------------------------- ### getCounter Source: https://github.com/forgesworn/spoken-token/blob/main/llms-full.txt Derives a counter from a Unix timestamp for time-based token rotation. ```APIDOC ## getCounter(timestampSec, rotationIntervalSec?) ### Description Derive the current counter from a Unix timestamp. Both parties compute the same value independently. ### Parameters - **timestampSec** (number) - Required - Unix timestamp in seconds - **rotationIntervalSec** (number?) - Optional - Rotation interval in seconds (default: 604800) ``` -------------------------------- ### Encode Bytes as Words Source: https://github.com/forgesworn/spoken-token/blob/main/llms-full.txt Encode a byte array into an array of words. Supports specifying the number of words and a custom wordlist. Custom wordlists must contain exactly 2048 entries. ```typescript import { encodeAsWords } from 'spoken-token' encodeAsWords(someBytes, 2) // → ['carbon', 'timber'] ``` -------------------------------- ### Verify Case-Insensitive Token Source: https://github.com/forgesworn/spoken-token/blob/main/PROTOCOL.md Verify a token where the input is case-insensitive, demonstrating that the verification process can handle different casing. ```plaintext verifyToken | JAZZ | SECRET_1 | test | 0 | — | 0 | valid (case-insensitive) ``` -------------------------------- ### counterFromEventId Source: https://context7.com/forgesworn/spoken-token/llms.txt Derives a deterministic counter from an event identifier using SHA-256 truncated to 32 bits. ```APIDOC ## counterFromEventId ### Description Derives a deterministic counter from an event identifier (e.g., booking ID, task ID, order number). Both parties derive the same counter from the same event ID without coordination. ### Parameters - **eventId** (string) - Required - The unique identifier for the event. ``` -------------------------------- ### verifyToken Source: https://github.com/forgesworn/spoken-token/blob/main/README.md Verifies a spoken or entered token against a shared secret. ```APIDOC ## verifyToken ### Description Verify a spoken or entered token. Returns an object containing the status and optional identity. ### Parameters - **secret** (Uint8Array | string) - Required - Shared secret - **context** (string) - Required - Domain separation string - **counter** (number) - Required - Time-based or usage counter - **input** (string) - Required - The token to verify - **identities** (string[]) - Optional - List of valid identities - **options** (object) - Optional - { encoding?, tolerance? } where tolerance accepts tokens within ±N counter steps (max 10) ### Response - **status** (string) - 'valid' or 'invalid' - **identity** (string) - The identity associated with the token if applicable ``` -------------------------------- ### counterToBytes Source: https://github.com/forgesworn/spoken-token/blob/main/llms-full.txt Serializes a counter to an 8-byte big-endian Uint8Array. ```APIDOC ## counterToBytes(counter) ### Description Serialise a counter to an 8-byte big-endian Uint8Array (same encoding as TOTP/RFC 6238). ### Parameters - **counter** (number) - Required - The counter value ``` -------------------------------- ### counterFromEventId Source: https://github.com/forgesworn/spoken-token/blob/main/llms-full.txt Derives a deterministic counter from an event identifier. ```APIDOC ## counterFromEventId(eventId) ### Description Derive a deterministic counter from an event identifier (e.g. a Nostr event ID or task ID). Uses SHA-256 truncated to 32 bits. ### Parameters - **eventId** (string) - Required - The event identifier ``` -------------------------------- ### Hex Encoding and Decoding Source: https://context7.com/forgesworn/spoken-token/llms.txt Convert byte arrays to hexadecimal strings and vice versa. Imports: bytesToHex, hexToBytes. ```typescript import { bytesToHex, hexToBytes } from 'spoken-token' // Hex encoding/decoding const hex = bytesToHex(secret) // → '0a1b2c3d...' const bytes = hexToBytes(hex) // → Uint8Array ``` -------------------------------- ### Time-Based Counter Derivation Source: https://github.com/forgesworn/spoken-token/blob/main/PROTOCOL.md Calculates a time-based counter for token derivation. The rotation interval determines how frequently the token changes. ```pseudocode counter = floor(unix_timestamp_seconds / rotation_interval_seconds) ``` -------------------------------- ### Encoding Functions Source: https://context7.com/forgesworn/spoken-token/llms.txt Low-level encoding functions for converting raw bytes to words, PINs, or hex strings. ```APIDOC ## Encoding Functions ### Description Utilities for converting raw bytes into human-readable formats like word phrases, PINs, or hex strings. ### Functions - **encodeAsWords(bytes, count)** - Encodes bytes into a list of words. - **encodeAsPin(bytes, digits)** - Encodes bytes into a numeric PIN string. - **encodeAsHex(bytes, length)** - Encodes bytes into a hex string. - **encodeToken(bytes, options)** - Generic encoder using a TokenEncoding configuration object. ``` -------------------------------- ### Encode Bytes to Words, PINs, or Hex Source: https://context7.com/forgesworn/spoken-token/llms.txt Low-level encoding functions for converting raw bytes to words, PINs, or hex strings. Use these for custom token formats or when specific encoding is required. ```typescript import { encodeAsWords, encodeAsPin, encodeAsHex, encodeToken, deriveTokenBytes, randomSeed } from 'spoken-token' const secret = randomSeed(32) const bytes = deriveTokenBytes(secret, 'test', 0) // Encode as words (1-16 words from 2048-word list) const oneWord = encodeAsWords(bytes, 1) // → ['carbon'] const twoWords = encodeAsWords(bytes, 2) // → ['carbon', 'timber'] const phrase = encodeAsWords(bytes, 3) // → ['carbon', 'timber', 'sunset'] ``` ```typescript // Encode as PIN (1-10 digits, bias < 1%) const pin4 = encodeAsPin(bytes, 4) // → '0478' const pin6 = encodeAsPin(bytes, 6) // → '047821' const pin8 = encodeAsPin(bytes, 8) // → '04782193' ``` ```typescript // Encode as hex (1-64 characters) const hex8 = encodeAsHex(bytes, 8) // → 'a3f2e1b0' const hex16 = encodeAsHex(bytes, 16) // → 'a3f2e1b0c4d5e6f7' ``` ```typescript // Generic encoder with TokenEncoding type const wordToken = encodeToken(bytes, { format: 'words', count: 2 }) // → 'carbon timber' const pinToken = encodeToken(bytes, { format: 'pin', digits: 6 }) // → '047821' const hexToken = encodeToken(bytes, { format: 'hex', length: 8 }) // → 'a3f2e1b0' ``` -------------------------------- ### Spoken Token Constants Source: https://github.com/forgesworn/spoken-token/blob/main/llms-full.txt Defines constants used within the spoken-token library for verification parameters and default settings. ```typescript MAX_TOLERANCE = 10 // Maximum allowed tolerance value for verifyToken MAX_COUNTER_OFFSET = 100 // Maximum allowed usage counter offset above time-based counter DEFAULT_ROTATION_INTERVAL = 604_800 // 7 days in seconds DEFAULT_ENCODING = { format: 'words', count: 1 } ``` -------------------------------- ### getCounter Source: https://context7.com/forgesworn/spoken-token/llms.txt Computes a time-based counter from a Unix timestamp. Both parties independently compute the same value without coordination. ```APIDOC ## getCounter ### Description Computes a time-based counter from a Unix timestamp. The default rotation interval is 7 days (604,800 seconds). ### Parameters - **timestamp** (number) - Required - The Unix timestamp. - **interval** (number) - Optional - The rotation interval in seconds. ``` -------------------------------- ### Encode Bytes as PIN Source: https://github.com/forgesworn/spoken-token/blob/main/llms-full.txt Encode a byte array into a string of digits. Supports specifying the number of digits. PIN encoding uses a lookup table to maintain modular bias below 1% for all digit counts. ```typescript import { encodeAsPin } from 'spoken-token' encodeAsPin(someBytes, 6) // → '047821' ``` -------------------------------- ### Word Encoding Logic Source: https://github.com/forgesworn/spoken-token/blob/main/PROTOCOL.md Derives a word from a 2-byte slice of a token digest using a wordlist. Repeated words are valid. ```pseudocode index = uint16_be(token_bytes, i * 2) mod 2048 word = wordlist[index] ``` -------------------------------- ### Verify Token with Tolerance Source: https://github.com/forgesworn/spoken-token/blob/main/PROTOCOL.md Verify a token with a specified tolerance, allowing for slight variations in the counter value. ```plaintext verifyToken | involve | SECRET_1 | test | 0 | — | 1 | valid (counter 1 within ±1) ``` -------------------------------- ### Event-Based Counter Derivation Source: https://github.com/forgesworn/spoken-token/blob/main/PROTOCOL.md Derives a deterministic counter from an event identifier using SHA-256. This method avoids the need for direct coordination between parties. ```pseudocode counter = uint32_be(SHA-256(utf8(event_id))[0:4]) ``` -------------------------------- ### Derive a PIN token Source: https://github.com/forgesworn/spoken-token/blob/main/llms-full.txt Derive a 6-digit PIN token by specifying the format and number of digits in the encoding options. ```typescript const pin = deriveToken(sharedSecret, 'door:entry', counter, { format: 'pin', digits: 6 }) // → '047821' ``` -------------------------------- ### Derive a token for rideshare pickup Source: https://github.com/forgesworn/spoken-token/blob/main/README.md Derives a single word token for verification purposes. ```typescript import { deriveToken, getCounter } from 'spoken-token' const counter = getCounter(Date.now() / 1000) // rotates every 7 days by default const word = deriveToken(sharedSecret, 'rideshare:pickup', counter) // → 'carbon' ``` -------------------------------- ### getCounter Source: https://github.com/forgesworn/spoken-token/blob/main/llms.txt Computes a time-based counter based on a timestamp and rotation interval. ```APIDOC ## getCounter(timestampSec, rotationIntervalSec?) ### Description Computes a time-based counter from a timestamp. Defaults to a 7-day rotation interval. ### Parameters - **timestampSec** (number) - Required - The timestamp in seconds. - **rotationIntervalSec** (number) - Optional - The interval in seconds for counter rotation (default: 604800). ``` -------------------------------- ### Derive a 6-Digit PIN Token Source: https://context7.com/forgesworn/spoken-token/llms.txt Derives a token as a 6-digit PIN. Specify the desired number of digits. ```typescript import { deriveToken, getCounter, randomSeed } from 'spoken-token' // Generate a shared secret (32 bytes recommended) const secret = randomSeed(32) // Get current counter (rotates every 7 days by default) const counter = getCounter(Date.now() / 1000) // Derive a 6-digit PIN const pin = deriveToken(secret, 'door:entry', counter, { format: 'pin', digits: 6 }) // → '047821' ``` -------------------------------- ### Derive Token Bytes API Source: https://github.com/forgesworn/spoken-token/blob/main/llms-full.txt Derives the raw 32-byte HMAC-SHA256 output without any encoding. This is useful when you need to apply custom encoding or use the bytes directly. ```APIDOC ## POST /api/token/derive/bytes ### Description Derives the raw 32-byte HMAC-SHA256 output without any encoding. This is useful when you need to apply custom encoding or use the bytes directly. ### Method POST ### Endpoint /api/token/derive/bytes ### Parameters #### Request Body - **secret** (Uint8Array | string) - Required - Shared secret (hex string or bytes, minimum 16 bytes) - **context** (string) - Required - Domain-separation string — must not contain null bytes - **counter** (number) - Required - Time-based or usage counter (uint32, 0–4294967295) - **identity** (string?) - Optional - Optional member identifier for per-member tokens ### Request Example ```json { "secret": "your_shared_secret_here", "context": "my-context", "counter": 1678886400, "identity": "user123" } ``` ### Response #### Success Response (200) - **bytes** (Uint8Array) - The raw 32-byte HMAC-SHA256 output. #### Response Example ```json { "bytes": "a3f2e1b0..." } ``` #### Error Handling - Throws `RangeError` if secret is too short or counter is out of range. - Throws `Error` if context contains null bytes. ``` -------------------------------- ### Wordlist Integrity Check (SHA-256 Hash) Source: https://github.com/forgesworn/spoken-token/blob/main/PROTOCOL.md The SHA-256 hash of the canonical English wordlist (en-v1). Used for integrity verification. ```text 0334930ebdfbc76e81ec914515d7567ca85738a6bf3069249d97df951d44661c ``` -------------------------------- ### Derive raw token bytes Source: https://github.com/forgesworn/spoken-token/blob/main/llms-full.txt Derive the raw 32-byte HMAC-SHA256 output without any encoding. This is useful for custom encoding or direct byte usage. ```typescript import { deriveTokenBytes } from 'spoken-token' const bytes = deriveTokenBytes(sharedSecret, 'my-context', counter) // → Uint8Array(32) — raw HMAC-SHA256 output ``` -------------------------------- ### Derive Raw Hex Token Source: https://github.com/forgesworn/spoken-token/blob/main/PROTOCOL.md Derive a raw hexadecimal token from a secret, context, and counter. This is a fundamental step in token generation. ```plaintext deriveTokenBytes | SECRET_1 | test | 0 | raw hex | ec02d24ed34bf06b679564bad8a13c599ccb07d25848c7c7cee3d33e028c8bfc ``` -------------------------------- ### Verify Identity-Bound Token Source: https://github.com/forgesworn/spoken-token/blob/main/PROTOCOL.md Verify a token that is bound to specific identities. The verification confirms both the token and the associated identity. ```plaintext verifyToken | vicious | SECRET_1 | test | 0 | ["alice", "bob"] | 0 | valid, identity: alice ``` -------------------------------- ### Derive Counter from Timestamp Source: https://github.com/forgesworn/spoken-token/blob/main/llms-full.txt Derive a counter from a Unix timestamp, useful for time-based token generation and verification. Supports custom rotation intervals for different time scales, from seconds to days. The default rotation interval is 7 days. ```typescript import { getCounter, DEFAULT_ROTATION_INTERVAL } from 'spoken-token' // Default: 7-day rotation interval const counter = getCounter(Date.now() / 1000) // → integer that changes every 7 days // 30-second rotation (TOTP-like) const counter30s = getCounter(Date.now() / 1000, 30) // 1-hour rotation const counterHourly = getCounter(Date.now() / 1000, 3600) // DEFAULT_ROTATION_INTERVAL === 604800 (7 days in seconds) ``` -------------------------------- ### Verify Invalid Token Source: https://github.com/forgesworn/spoken-token/blob/main/PROTOCOL.md Attempt to verify a token that does not match the secret, context, or counter, resulting in an invalid status. ```plaintext verifyToken | wrong | SECRET_1 | test | 0 | — | 0 | invalid ``` -------------------------------- ### Derive Raw Token Bytes Source: https://context7.com/forgesworn/spoken-token/llms.txt Derives the raw 32-byte HMAC-SHA256 output without any encoding. Useful for custom cryptographic operations. ```typescript import { deriveTokenBytes, randomSeed } from 'spoken-token' const secret = randomSeed(32) const counter = 12345 // Get raw 32-byte HMAC-SHA256 output const bytes = deriveTokenBytes(secret, 'my-context', counter) // → Uint8Array(32) — raw HMAC-SHA256 digest // With identity binding const identityBytes = deriveTokenBytes(secret, 'my-context', counter, 'user@example.com') // → Different bytes for each identity ``` -------------------------------- ### HMAC-SHA256 for Group-Wide Tokens Source: https://github.com/forgesworn/spoken-token/blob/main/PROTOCOL.md Derives a 32-byte token using HMAC-SHA256 with a shared secret, context string, and a counter. Ensure context and counter are correctly formatted. ```pseudocode token_bytes = HMAC-SHA256(secret, utf8(context) || counter_be32) ``` -------------------------------- ### Derive a 2-Word Phrase Token Source: https://context7.com/forgesworn/spoken-token/llms.txt Derives a token as a two-word phrase for increased entropy. Specify format and count. ```typescript import { deriveToken, getCounter, randomSeed } from 'spoken-token' // Generate a shared secret (32 bytes recommended) const secret = randomSeed(32) // Get current counter (rotates every 7 days by default) const counter = getCounter(Date.now() / 1000) // Derive a 2-word phrase for higher entropy const phrase = deriveToken(secret, 'courier:handoff', counter, { format: 'words', count: 2 }) // → 'carbon timber' ``` -------------------------------- ### deriveTokenBytes Source: https://context7.com/forgesworn/spoken-token/llms.txt Derives raw 32-byte HMAC-SHA256 output without encoding. Useful for custom encoding or direct cryptographic use. ```APIDOC ## deriveTokenBytes ### Description Derives raw 32-byte HMAC-SHA256 output without encoding. Useful when you need the raw bytes for custom encoding or direct cryptographic use. ### Method `deriveTokenBytes` ### Parameters #### Path Parameters None #### Query Parameters None #### Request Body None ### Request Example ```typescript import { deriveTokenBytes, randomSeed } from 'spoken-token' const secret = randomSeed(32) const counter = 12345 // Get raw 32-byte HMAC-SHA256 output const bytes = deriveTokenBytes(secret, 'my-context', counter) // → Uint8Array(32) — raw HMAC-SHA256 digest // With identity binding const identityBytes = deriveTokenBytes(secret, 'my-context', counter, 'user@example.com') // → Different bytes for each identity ``` ### Response #### Success Response (200) - **bytes** (Uint8Array) - The raw 32-byte HMAC-SHA256 digest. #### Response Example ```json { "bytes": "" } ``` ``` -------------------------------- ### Verify Token with No Tolerance (Invalid) Source: https://github.com/forgesworn/spoken-token/blob/main/PROTOCOL.md Attempt to verify a token with no tolerance when the counter has shifted, resulting in an invalid verification. ```plaintext verifyToken | involve | SECRET_1 | test | 0 | — | 0 | invalid (counter 1, no tolerance) ``` -------------------------------- ### Compute Time-Based Counter Source: https://context7.com/forgesworn/spoken-token/llms.txt Computes a time-based counter from a Unix timestamp. Both parties independently compute the same value without coordination. The default rotation interval is 7 days. ```typescript import { getCounter, DEFAULT_ROTATION_INTERVAL } from 'spoken-token' // Default: 7-day rotation interval const weeklyCounter = getCounter(Date.now() / 1000) // → integer that changes every 7 days ``` ```typescript // 30-second rotation (TOTP-style for real-time calls) const counter30s = getCounter(Date.now() / 1000, 30) ``` ```typescript // 5-minute rotation (courier handoff) const counter5min = getCounter(Date.now() / 1000, 300) ``` ```typescript // 1-hour rotation const hourlyCounter = getCounter(Date.now() / 1000, 3600) ``` ```typescript // 24-hour rotation (daily team check-in) const dailyCounter = getCounter(Date.now() / 1000, 86400) ``` ```typescript // DEFAULT_ROTATION_INTERVAL === 604800 (7 days in seconds) ``` -------------------------------- ### Encode Bytes as Hex String Source: https://github.com/forgesworn/spoken-token/blob/main/llms-full.txt Converts a byte array to its hexadecimal string representation. Specify the desired length of the output hex string. ```typescript import { encodeAsHex } from 'spoken-token' encodeAsHex(someBytes, 8) // → 'a3f2e1b0' ``` -------------------------------- ### Constant-Time Comparison Source: https://context7.com/forgesworn/spoken-token/llms.txt Perform constant-time comparisons for security-sensitive operations to prevent timing attacks. Imports: timingSafeEqual, timingSafeStringEqual. ```typescript import { timingSafeEqual, timingSafeStringEqual } from 'spoken-token' // Constant-time comparison (prevents timing attacks) const equal = timingSafeEqual(secret, decoded) const strEqual = timingSafeStringEqual('carbon', 'carbon') ``` -------------------------------- ### Derive a Spoken Token Source: https://github.com/forgesworn/spoken-token/blob/main/llms.txt Derive a spoken word token from a shared secret, context, and a time-based counter. The counter rotates every 7 days by default. Ensure both parties use the same shared secret and context. ```typescript import { deriveToken, getCounter } from 'spoken-token' // Both parties derive the same word from the shared secret const counter = getCounter(Date.now() / 1000) // rotates every 7 days const word = deriveToken(sharedSecret, 'rideshare:pickup', counter) // => 'carbon' ```