@hyperfrontend/network-protocol/node/v3

v3

Node.js-side v3 protocol: a session-keyed envelope with no shared secret, wired to the Node.js crypto module.

Overview

createProtocol(logger) returns a ProtocolProvider. Bound to a session by createChannel, each instance mints a random 32-byte nonce and an ephemeral P-256 key pair and advertises them in a 99-byte hello frame (channel.hello()); the peer's hello goes to channel.acceptHello(frame), after which two AES-GCM-256 keys, one per direction, are derived from the agreement with HKDF-SHA256 under info strings that name the protocol and both identities. The first hello keys the session, a byte-for-byte repeat of it is a duplicate, anything else is rejected, and a live session is never rekeyed.

Every sealed frame carries its counter in the clear as the nonce and authenticates its ten-byte header; a frame whose counter is not above the last accepted one is rejected before decryption, and a frame from any other session fails to authenticate.

V3 is the protocol's definition, { id: 'v3', version: 3 }: the identifier a session names and the byte every frame starts with.

What v3 protects against

A party that can only listen to the hello exchange and the traffic cannot read or forge frames. A party that can post its own hello before the peer's arrives can complete a v3 handshake as that peer, because nothing authenticates who said hello. Choose /node/v4 when the counterpart must be authenticated. Neither protocol hides the hello: nonces and public keys are public by design.

Usage

import { parentPort } from 'node:worker_threads'
import { createProtocol } from '@hyperfrontend/network-protocol/node/v3'
import { createChannel } from '@hyperfrontend/network-protocol/node/channel'
import { createLogger } from '@hyperfrontend/logging'

const channel = createChannel('main-to-worker', {
  send: (frame) => parentPort.postMessage(frame, [frame.buffer]),
  receive: (packet) => handle(packet.data.message),
  protocolProvider: createProtocol(createLogger({ level: 'info' })),
  session: { protocol: 'v3', role: 'responder', localId, peerId },
})
parentPort.postMessage(await channel.hello())
parentPort.on('message', (frame: Uint8Array) => (channel.isHello(frame) ? channel.acceptHello(frame) : channel.receive(frame)))

Notes

  • Session setup costs one ECDH agreement plus one HKDF expansion per direction; each frame costs one AES-GCM operation. Keys are derived once, on the first seal or open after the peer's hello is accepted, and product traffic sent before then waits inside the seal stage.
  • A rejected frame reaches onDrop with a ProtocolError as cause; getProtocolErrorCode(drop.cause) from /security names the reason (unsupported-version, replayed, authentication-failed, malformed, counter-exhausted, or invalid-session).
  • The browser counterpart lives at /browser/v3 and produces identical frames.

API Reference§

ƒ Functions

§function

createProtocolProviderStore(): ProtocolProviderStore

Creates a store for managing protocol provider registrations. Provides methods to register, retrieve, and list protocol providers.

Returns

ProtocolProviderStore
A ProtocolProviderStore with methods for managing protocol providers

Example

Creating and using a protocol provider store

const store = createProtocolProviderStore()
store.add('websocket', myProtocolProvider)
const provider = store.getByName('websocket')
§function

createV3ProtocolFactory(crypto: SessionCrypto): (logger: Logger) => ProtocolProvider

Creates the v3 protocol factory for a platform.
v3 keys each session from an ephemeral key agreement alone: a party that only listens to the hello exchange and the traffic cannot read it, while a party that can post its own hello to a window before the peer's arrives can stand in for the peer. It authenticates frames, rejects replays, and binds traffic to one session, but does not authenticate the peer.

Parameters

NameTypeDescription
§crypto
SessionCrypto
The platform primitives

Returns

(logger: Logger) => ProtocolProvider
createProtocol(logger), which returns the provider a channel binds to a session

Example

Composing v3 in a browser entry

export const createProtocol = createV3ProtocolFactory(crypto)
const protocolProvider = createProtocol(logger)
§function

isValidName(name: string): boolean

Validates whether the provided name is valid for protocol registration. The name must be a non-empty string.

Parameters

NameTypeDescription
§name
string
The name to validate

Returns

boolean
True if the name is a non-empty string, false otherwise

Example

Validating protocol names

isValidName('websocket')
// => true

isValidName('')
// => false
§function

isValidProtocol(protocol: unknown): ValidProtocolResult

Validates whether a protocol object contains all required function properties, stopping at the first that is missing or not a function.

Parameters

NameTypeDescription
§protocol
unknown
The protocol object to validate

Returns

ValidProtocolResult
An object mapping each protocol property to its validation status (true if valid, false if invalid, undefined if not yet checked)

Example

Validating a protocol object

const result = isValidProtocol(myProtocol)
// => { seal: true, open: true, hello: true, isHello: true, acceptHello: true, send: true, receive: true, getLogger: true }

const invalid = isValidProtocol({})
// => { seal: false, open: undefined, ... }
§function

isValidProtocolProvider(protocolProvider: unknown): boolean

Validates whether the provided value is a valid protocol provider. A protocol provider must be a function that creates protocol instances.

Parameters

NameTypeDescription
§protocolProvider
unknown
The value to validate as a protocol provider

Returns

boolean
True if the value is a function, false otherwise

Example

Validating a protocol provider function

isValidProtocolProvider(() => protocol)
// => true

isValidProtocolProvider('not-a-function')
// => false
§function

isValidReceiveFn(receive: unknown): boolean

Validates whether the provided value is a valid receive function. The receive function must be callable.

Parameters

NameTypeDescription
§receive
unknown
The value to validate as a receive function

Returns

boolean
True if the value is a function, false otherwise

Example

Validating a receive function

isValidReceiveFn((packet) => console.log(packet))
// => true

isValidReceiveFn(null)
// => false
§function

isValidSendFn(send: unknown): boolean

Validates whether the provided value is a valid send function. The send function must be callable.

Parameters

NameTypeDescription
§send
unknown
The value to validate as a send function

Returns

boolean
True if the value is a function, false otherwise

Example

Validating a send function

isValidSendFn((packet) => websocket.send(packet))
// => true

isValidSendFn('not-a-function')
// => false

Interfaces

§interface

ProtocolProviderEntry

Entry in a protocol provider store, associating a provider with identifiers.

Properties

§readonly id:string
Unique identifier for this entry
§readonly name:string
Human-readable name for this provider
§readonly provider:ProtocolProvider<T>
The protocol provider instance
§interface

ProtocolProviderStore

Store for managing protocol providers with lookup by name or ID.

Properties

§readonly add:(name: string, protocolProvider: ProtocolProvider<T>) => void
Registers a new provider with the given name
§readonly clear:() => void
Removes all providers
§readonly existsById:(id: string) => boolean
Checks if a provider exists by ID
§readonly existsByName:(name: string) => boolean
Checks if a provider exists by name
§readonly getById:(id: string) => ProtocolProvider<T>
Retrieves a provider by ID
§readonly getByName:(name: string) => ProtocolProvider<T>
Retrieves a provider by name
§readonly list:unknown
List of all registered provider entries
§readonly removeById:(id: string[]) => void
Removes providers by ID
§readonly removeByName:(name: string[]) => void
Removes providers by name
§interface

SessionCrypto

The platform primitives a session protocol is composed from

Properties

§readonly createKeyAgreement:() => Promise<KeyAgreementLike>
Mints an ephemeral key agreement
§readonly expandKey:(ikm: Uint8Array, salt: Uint8Array, info: Uint8Array, usages: unknown) => Promise<CryptoKey>
HKDF expansion into a usage-restricted AES-GCM key
§readonly getRandomValues:(byteLength: number) => Uint8Array
Cryptographically secure random bytes
§readonly open:(key: CryptoKey, nonce: Uint8Array, additionalData: Uint8Array, sealed: Uint8Array) => Promise<Uint8Array<ArrayBufferLike>>
AES-GCM open under a key, nonce, and additional data
§readonly seal:(key: CryptoKey, nonce: Uint8Array, additionalData: Uint8Array, plaintext: Uint8Array) => Promise<Uint8Array<ArrayBufferLike>>
AES-GCM seal under a key, nonce, and additional data
§readonly stretchPassword:(password: string, salt: Uint8Array, options?: StretchOptions) => Promise<Uint8Array<ArrayBufferLike>>
PBKDF2 password stretching into raw bits
§readonly utf8Decode:(bytes: Uint8Array) => string
UTF-8 bytes to text
§readonly utf8Encode:(text: string) => Uint8Array
UTF-8 text to bytes

Types

§type

ValidProtocolResult

Result object mapping each protocol property to its validation status.
type ValidProtocolResult = mapped

Variables

§type

createProtocol

§type

V3

The v3 protocol's negotiated identifier and frame version byte