YUME Internet freedom by FixCraft
On this page

YUME Transport Protocol 1 kernel

This is the normative development contract for the YTP/1 kernel, session engine, native providers and internal native endpoint.

The authority for this contract is src/ytp/, src/engine/session_engine.*, src/runtime/native_endpoint.*, and the provider sources openssl_security_provider.*, ytp1_crypto.*, tls13_secure_channel.*, h2_duplex_carrier.*, h2_web_front_door.*, ytp1_h2_admission.*, cover_site.* and asio_* under src/providers/, together with their focused tests and the checked-in kernel vectors. Source and executable tests outrank this document if they disagree.

The kernel implements canonical encodings, structural validation, bounds, stream rules, fixed suite metadata, domains, and key-schedule input encoding. The session engine implements the authenticated record lifecycle and multiplexing over a carrier. Native providers implement the fixed hybrid handshake, a memory-BIO TLS 1.3 secure channel, duplex H2 carrier behavior, client TCP ByteChannel, and direct TCP/connected-UDP routing. A native FrontDoor composes accepted TCP, TLS, genuine static cover and admission into the H2 carrier. The internal NativeEndpoint composes both bootstrap roles, protected schema-1 credentials, per-identity named-handler authorization, bounded automatic server accepts, session ownership and shutdown drain. It also composes configured direct TCP/UDP adapters whose schema-1 destinations are authorized before resolution and for every resolved address. The experimental schema-1 C ABI backend carries named streams, named packets and routed TCP/UDP requests, while refusing adapter declarations. Development yumed and yume processes compose direct routes, SOCKS5 CONNECT and UDP ASSOCIATE, forwards, server modules and managed Linux TUN. Qualification is tracked in implementation status.

Conventions and compatibility

The key words MUST, MUST NOT, SHOULD, and MAY describe the behavior accepted or emitted by the current kernel.

All multibyte integers are unsigned and big-endian. Lengths count octets. Decoders require exact input consumption: missing bytes are truncated input and extra bytes are trailing data. Reserved values and bits fail closed. Bounds are checked before variable-length data is copied or returned.

YTP/1 is wire version 1. It has no compatibility path, downgrade, alias, or suite negotiation for YUME transport v2, AUTH v2, relay v2, or their domains. Product version, configuration schema 1, C ABI v1, provider versions, and YTP/1 are independent version axes.

H2 admission v1

Admission proves possession of the separate 32-byte admission key before the server starts YTP AUTH. It does not authenticate a transport identity or grant a service. providers/ytp1_h2_admission.* owns this construction; admission/h2_admission.* owns the shared path parser, HMAC primitive and bounded replay cache. The client carrier builds the proof from its active TLS channel. providers/h2_web_front_door.* verifies admission on the actual accepted TLS connection before transferring it to the H2 carrier.

The proof path is / followed by 64 lowercase hexadecimal token characters, /, and 64 lowercase hexadecimal nonce characters. No query, trailing data, uppercase hex or alternative encoding is accepted. The nonce is 32 fresh cryptographically random bytes for each client carrier creation.

The HMAC-SHA256 key is exactly 32 bytes. Its canonical input is:

u16 domain_length
u8  domain[domain_length] = "yume/ytp/1/h2-web/admission/v1"
u16 server_name_length
u8  canonical_server_name[server_name_length]
u16 exporter_length = 32
u8  exporter[32]
u8  nonce[32]

The server name is lowercase ASCII DNS text, at most 253 bytes, with nonempty labels of at most 63 bytes, no trailing root dot, and no leading or trailing hyphen in a label. The client uses the same name its TLS provider authenticated; the server checks HTTP authority against received TLS SNI and its listener port. Both obtain 32 exporter bytes locally from the completed TLS connection using EXPORTER-yume/ytp/1/h2-web-admission/v1 and an empty context. The exporter is never a peer-supplied field. Token comparison is constant time.

Wall-clock buckets and browser evidence-profile IDs are absent. The exporter binds the proof to one TLS connection, avoiding a clock-synchronization requirement. Browser geometry remains independent of this admission encoding. It shares no field or domain with transport v2’s admission token.

A native front door MUST verify the proof and reserve its nonce atomically in one process-shared bounded replay cache before promotion. Cache expiry uses monotonic time; duplicates, saturation, clock regression, overflow and failed allocation refuse admission without evicting a live nonce. At most one successful carrier promotion is allowed for a TLS connection throughout its lifetime. A TTL cache alone cannot enforce that rule after an entry expires. Missing, invalid, replayed or resource-exhausted admission follows the genuine configured cover path and never starts AUTH. The native FrontDoor implements these ingress rules with a configured immutable static website; admission alone does not authenticate a YTP peer.

At the static-site boundary, methods other than GET and HEAD receive the cover reference’s fixed 405 response, including failed CONNECT admission. This does not change proof verification, replay reservation or successful promotion. GET and HEAD retain configured route and not-found handling.

Streams and frames

Stream identifiers

A stream identifier is an unsigned 31-bit integer in the range 0 through 0x7fffffff.

  • Stream 0 is the session-control stream.
  • A client owns odd application stream identifiers, beginning with 1.
  • A server owns even application stream identifiers, beginning with 2.
  • Each endpoint advances its locally owned identifier by two. Advancing past the 31-bit range is exhaustion and MUST fail.
  • OPEN ownership validation rejects stream zero and an application stream not owned by the opener.

Frame decoding enforces control-versus-application stream class. It does not infer the peer role and therefore does not itself enforce OPEN ownership; the caller MUST apply the separate ownership check when admitting an OPEN.

Frame header

Every frame begins with this canonical 12-octet header:

u8  version = 1
u8  record_type
u16 flags = 0
u32 stream_id
u32 payload_length

The stream identifier’s high bit and every flag bit are reserved and MUST be zero. payload_length MUST NOT exceed the bound supplied by the caller. The kernel default is 1 MiB (1 << 20), but a runtime MAY impose a smaller bound. The record decoder accepts exactly 12 + payload_length octets and returns a borrowed view of the payload.

H2-duplex carrier framing v1

The fixed YTP/1 suite carries each record through a carrier-private framing envelope before WebSocket and HTTP/2 encoding:

u8  magic[4] = "YCR\\0"
u8  envelope_version = 1
u8  flags = 0
u16 reserved = 0
u32 record_length
u8  record[record_length]

record_length is unsigned big-endian, must be nonzero, and must not exceed the carrier’s immutable record bound. The receiver validates all 12 header octets before allocating or retaining the record payload and rejects wrong magic/version, nonzero flags/reserved bytes, oversize lengths, truncated records, and terminal trailing state. The header’s HTTP/2 receive credit may be retired after validation; payload credit stays move-owned by the resulting ReceivedRecord until the engine or application releases it.

This envelope preserves one complete Carrier record across arbitrary secure channel reads and WebSocket message fragmentation/coalescing. WebSocket message boundaries and captured browser geometry are never YTP semantics and may change with an independently qualified profile. This framing is not a cryptographic or KDF domain and does not provide authentication by itself.

The implemented record type and stream-class assignments are:

Value Record Required stream class
1 AUTH control (0)
2 AUTH_RESULT control (0)
3 CAPABILITIES control (0)
4 OPEN application (nonzero)
5 DATA application (nonzero)
6 PACKET application (nonzero)
7 CLOSE application (nonzero)
8 CONNECTION_CREDIT control (0)
9 STREAM_CREDIT application (nonzero)
10 REKEY_INIT control (0)
11 REKEY_ACK control (0)
12 PING control (0)
13 PONG control (0)

Unknown record types are rejected. The kernel defines payload codecs only for OPEN, CAPABILITIES, credit updates, and the standalone AUTH TLV format below. It does not yet define or validate complete payload semantics for every record in this table.

Post-AUTH record protection

AUTH and AUTH_RESULT flights use the canonical frame directly. After AUTH, the carrier record for every frame except REKEY_ACK has this envelope:

u8  envelope_schema = 1
u24 reserved = 0
u32 directional_epoch
u64 directional_sequence
u8  aead_ciphertext_and_tag[]

The AEAD plaintext is exactly one canonical YTP/1 frame. Epoch and sequence form the provider’s record-key token and are bound into its AAD. Sequence is global and strictly increasing for each direction; it does not restart when the epoch advances. A token is accepted exactly once and the provider derives a fresh AES-256-GCM key and nonce for it. A malformed envelope, wrong epoch, replayed or out-of-order sequence, invalid tag, or malformed plaintext is a terminal protocol failure.

REKEY_ACK is the sole post-AUTH exception: it is carried as one bare canonical YTP/1 control frame in Active state. Its fixed provider payload contains a candidate-new-root HMAC described under directional rekey below, and the TLS carrier still protects the outer channel. A protected REKEY_ACK and every other bare post-AUTH frame are rejected. The exception permits opposite directions to rekey simultaneously without retaining a retired root merely to open a crossed acknowledgement.

OPEN payload

An OPEN payload is at most 512 octets:

u8  schema = 1
u8  service_kind
u8  transport
u8  address_kind
u16 service_name_length
u16 destination_length
u8  service_name[service_name_length]
u8  destination[destination_length]

service_kind is 1 for a byte stream or 2 for a packet channel. transport is 0 for none, 1 for TCP, or 2 for UDP. address_kind is 0 for none, 1 for IPv4, 2 for IPv6, or 3 for DNS.

The service name MUST contain 1 through 128 octets in canonical lowercase ASCII namespace form. A name is one or more segments separated by .. Every segment begins and ends with an ASCII lowercase letter or digit; - and _ MAY occur only inside a segment. Uppercase, Unicode, empty segments, control bytes, /, and : are invalid. Names are compared as exact octets with no case folding or normalization.

A named service with no destination MUST set both transport and address kind to zero and use a zero-length destination. Otherwise the transport and address kind MUST both be nonzero, the port MUST be nonzero, and:

  • a byte-stream destination MUST use TCP;
  • a packet-channel destination MUST use UDP;
  • IPv4 is u16 port || 4 address octets and has destination length 6;
  • IPv6 is u16 port || 16 address octets and has destination length 18;
  • DNS is u16 port || u8 dns_length || dns_name, and dns_length MUST equal destination_length - 3.

A DNS name contains 1 through 253 ASCII octets. It MUST be lowercase, consist only of a through z, 0 through 9, hyphen, and dot, and contain labels of 1 through 63 octets. A label MUST NOT begin or end with a hyphen. Empty labels, a leading or trailing dot, uppercase, non-ASCII input, and a trailing root dot are rejected. The kernel performs no IDNA conversion.

OPEN acceptance

Both per-stream receive windows begin at zero. Sending OPEN does not accept it and the opener MUST NOT send a stream-credit update, DATA, or PACKET until it receives the receiver’s first STREAM_CREDIT. The receiver MUST first complete per-OPEN authorization and handler acceptance; a destination-bearing service MUST establish its authorized destination connection before accepting. The receiver’s first STREAM_CREDIT is the authenticated acceptance barrier. A refusal uses terminal CLOSE and grants no stream credit. Authorization refusal, including a denied resolved destination address, uses the unauthorized close code. Failure to establish an otherwise authorized route uses the handler-failure close code.

After receiving acceptance, the opener publishes its initial STREAM_CREDIT before reporting successful OPEN to its application. The receiver can then write within that reverse-direction grant. Pending OPENs count against the session’s pending-open and stream bounds until accepted or refused. Local cancellation before acceptance sends a terminal CLOSE and completes that pending OPEN once; crossed acceptance cannot revive it. If OPEN is still unpublished behind the outbound rekey barrier, cancellation drops that OPEN without sending CLOSE for a stream the peer has never observed.

CLOSE payload

A CLOSE payload is exactly one octet:

u8 close_code

The codes are:

Code Meaning Effect
0 directional FIN The sender will transmit no more DATA or PACKET records on this stream. The peer may continue writing in the other direction.
1 unauthorized Terminal OPEN rejection or stream abort.
2 unsupported Terminal OPEN rejection or stream abort.
3 handler failure Terminal service failure and stream abort.
4 application abort Terminal abort requested by the application.
5 settled Ordered confirmation that the sender will publish no further records for this stream.

Code 0 closes only the sender’s write side. Already accepted inbound records remain readable. After both directional FINs and draining queued inbound data, each side sends code 5 and retains the stream’s closing state until receiving the peer’s code 5. A crossed terminal CLOSE does not discharge that peer settlement obligation, even if the local code 5 has already been sent. Returning application-held credit after closure restores connection credit only.

Codes 1 through 4 terminate both directions and fail pending OPEN and I/O. The receiver sends code 5 after settling application ownership. The initiator retains the closing entry until that acknowledgement arrives. If terminal CLOSEs cross, each side sends code 5 and waits for the other’s code 5. A side sends at most one settlement acknowledgement for each stream.

A terminal CLOSE or code 5 MUST follow every already-published record for that stream in the ordered record direction; no later DATA, PACKET or STREAM_CREDIT may be published in that direction. While waiting for the peer barrier, a locally closed stream validates crossed records against its service kind and remaining granted credit. Crossed DATA/PACKET is discarded and returns only connection credit; crossed stream credit remains bounded and cannot reopen the stream. Closing entries count against the session stream bound, so a peer that withholds settlement cannot cause unbounded tombstone retention.

An unknown stream ID, a repeated FIN, an unexpected or repeated settlement, an unknown close code, data after the peer’s FIN, or DATA, PACKET or STREAM_CREDIT after the peer’s terminal barrier is a protocol failure. A required crossed settlement acknowledgement remains valid after terminal CLOSE. Refusal does not invalidate other streams or prevent a subsequent authorized OPEN on the same session.

Capability manifest

A canonical capability manifest is at most 64 KiB and contains at most 256 entries:

u8  schema = 2
u8  flags = 0
u16 entry_count
u32 max_epoch_bytes
repeat entry_count:
    u8  service_kind
    u8  flags = 0
    u16 service_name_length
    u32 max_concurrent_streams
    u8  service_name[service_name_length]

Service kinds and names have the same rules as OPEN. max_concurrent_streams MUST be in 1..1048576 inclusive.

Entries are ordered first by unsigned service-name octets and then by numeric service kind. The encoder sorts entries into that order. The decoder rejects out-of-order entries and duplicate (service_name, service_kind) keys. The same name MAY appear once for each distinct service kind. An empty manifest that accepts 1 MiB epochs is the eight-octet value 02 00 00 00 00 10 00 00.

max_epoch_bytes is the most protected payload the sender accepts in one directional epoch, in either direction. It MUST be a power of two from 1 MiB (1048576) through 64 MiB (67108864). Each side advertises its own value in its AUTH flight, and both directions of the session use the smaller of the two values, so neither side sends or accepts a larger epoch than the other allows. Schema 1, which had no epoch field, is rejected.

A capability is authenticated advertisement, not authorization. A runtime MUST still apply its resource and per-OPEN authorization policy.

Flow-credit update

Both connection-credit and stream-credit updates use exactly one big-endian u32 increment. An increment MUST be in 1..1073741824 (1..2^30) inclusive. Zero, a larger value, a shorter payload, and a longer payload are rejected.

A receiver MUST NOT let the credit it has granted and the peer has not yet used exceed 2^30 octets on one stream or on the connection. A sender that would hold more treats the update as a protocol failure. Each side sizes its own receive windows, so a sender does not compare a grant with its own configuration.

The kernel encodes individual increments only. Window accounting, overflow handling, backpressure, and when credit is returned are runtime responsibilities and are not specified by this codec.

The native engine starts each stream’s receive window at 256 KiB and the connection’s at 4 MiB, both capped by the byte budget (limits.max_queued_bytes). It returns consumed credit in one update once half a window is consumed, or once the window less one frame is consumed if that comes first, so a peer holding less than a frame of credit never waits for it. A packet cannot be split to spend the last of a window, so on a packet channel the halves are those of the whole packets that fit, measured by the largest packet the peer has sent. Half the whole window could exceed that by less than a packet, and a sender left short of one packet would then get a single return, about half its window, per round trip. When two returns on a window come less than two round trips apart, the window was limiting the sender, and the engine doubles it, as Chromium does. A stream window can reach two thirds of the budget and the connection window the whole budget. The connection window stays at least one and a half times the largest stream window, and no window shrinks. If the peer has less connection credit than one maximum frame, the engine returns any already-consumed connection credit even below the usual return point. It checks both record arrival and application release, since the last record can arrive on an unread stream after useful records have been consumed. Application-held bytes keep their credit until release; an early return neither enlarges the window nor changes the configured return count. Otherwise, consumed credit below the return point waits at the receiver, so a sender held back by a window that can no longer grow moves about half of it per round trip. limits.credit_returns_per_window, 2 by default, 4 or 8, makes such a window return credit every half, quarter or eighth of itself, so the sender moves about a half, three quarters or seven eighths of it per round trip and the receiver sends that many more credit records. The engine times round trips locally, from its AUTH flight to the peer’s answer and from each REKEY_INIT to its ACK, keeps the smallest sample of the last ten seconds, and sends no record to take them.

The native engine does not fail a session because its own records find the outbound byte budget full. DATA, PACKET, OPEN and pending writes leave room for one credit or CLOSE record, beside the room kept for directional rekey. When the budget is full, an acceptance or returned STREAM_CREDIT, a CONNECTION_CREDIT, a FIN, a terminal CLOSE or a settlement becomes an obligation the engine still owes. It sends owed records as earlier records finish sending, under the ordering rules above. A window keeps at most limits.credit_returns_per_window grants waiting to be sent, and further returns join the next grant, so each stream waits on a bounded number of records. A closing stream keeps its entry, which counts against the stream bound, until everything it owes has been sent. A local OPEN that does not fit fails alone with ResourceExhausted.

Fixed security composition

YTP/1 exposes one exact suite identifier:

YTP/1:TLS13:H2:ED25519+ML-DSA-87:X25519+ML-KEM-1024:HKDF-SHA256:AES-256-GCM

This is a protocol constant, not a negotiation offer or provider-selection string. The required 24-octet security-parameter value is:

01 01 01 01 01 01 01 01 01 20 0c 10 20 20 20 20
06 20 06 20 00 40 01 00

In order, these octets declare parameter schema 1; composite authentication; Ed25519; ML-DSA-87; hybrid establishment; X25519; ML-KEM-1024; HKDF-SHA256; AES-256-GCM; a 32-octet AEAD key; a 12-octet nonce; a 16-octet tag; 32-octet access PSK, exporter, X25519 shared secret, and ML-KEM shared secret; 1568-octet ML-KEM-1024 public key and ciphertext; at most 64 prepared/in-flight ratchet epochs; mandatory one-use message keys; and a zero reserved octet. Every octet MUST match exactly.

The provider openssl35.ytp1-security enforces this exact composition. It creates a private OpenSSL library context, loads only the instance-local default provider, requires every named algorithm, and has no provider fallback or suite negotiation. The native programs and C ABI use this provider. Its production qualification remains open.

Domains

These byte strings are fixed, case-sensitive constants:

yume/ytp/1/auth-signature/v1
yume/ytp/1/composite-identity/v1
yume/ytp/1/auth-role/v1
yume/ytp/1/transcript/v1
yume/ytp/1/root/v1
yume/ytp/1/psk/v1
yume/ytp/1/handshake-confirmation/v1
yume/ytp/1/c2s-root/v1
yume/ytp/1/s2c-root/v1
yume/ytp/1/message/v1
yume/ytp/1/aad/v1
yume/ytp/1/ratchet/v1
EXPORTER-yume/ytp/1/channel-binding/v1

The kernel embeds the root domain in the key-schedule input and checks the domain constants in its focused test. The security provider uses these domains for composite identity, transcript, signatures, PSK authentication, hybrid handshake confirmation, directional roots, per-record material and AAD, and directional rekey. Implementations MUST NOT substitute formulas or yume/2.0/... labels from the transport-v2 design.

AUTH TLV encoding

The standalone canonical AUTH encoding is at most 64 KiB:

u8  schema = 1
u8  message_type
u16 field_count
u32 field_bytes
repeat field_count:
    u16 field_id
    u16 flags
    u32 value_length
    u8  value[value_length]

field_bytes MUST equal the exact remaining payload size. Field IDs MUST be nonzero and strictly increasing. Duplicate or out-of-order fields are rejected. At most 32 fields are permitted.

Flag bit 0 (0x0001) marks a critical field; all other bits are reserved. Unknown critical fields are rejected. A bounded unknown noncritical field is retained by decode and survives re-encoding. Every implemented known field is critical.

The accepted message-type values are:

Value Name
1 CHALLENGE
2 RESPONSE
3 ACCEPTED
4 REKEY_INIT
5 REKEY_ACK

The encoder inserts fields 1 through 3; callers MUST NOT supply them:

ID Field Required value
1 suite ID exact suite string above
2 security parameters exact 24-octet value above
3 sender role one octet: client 1 or server 2
4 transcript hash 32 octets
5 identity 1 through 16384 opaque octets
6 composite signature 4691 octets: Ed25519 64 plus ML-DSA-87 4627
7 ML-KEM public key 1568 octets
8 ML-KEM ciphertext 1568 octets
9 X25519 public key 32 octets
10 capability manifest a canonical manifest as specified above
11 nonce 32 octets
12 PSK authenticator 32 octets
13 hybrid key confirmation 32 octets

The codec validates field widths and canonical capability bytes. It treats an identity as bounded opaque bytes and does not parse a composite identity. It does not itself enforce a message-specific set of required or forbidden fields: an encoding containing only fields 1 through 3 is structurally valid for any known message type.

The OpenSSL provider accepts only these exact critical caller-field sets, in addition to mandatory fields 1 through 3:

Message Sender Exact caller field IDs
CHALLENGE server 4, 5, 6, 7, 9, 10, 11
RESPONSE client 4, 5, 6, 8, 9, 10, 12, 13
ACCEPTED server 4, 6, 13

It rejects missing, additional, noncritical, reordered, noncanonical, wrong-role, and wrong-flight fields. Both Ed25519 and ML-DSA-87 signatures must verify. RESPONSE also requires the authorized key’s access-PSK authenticator, X25519 and ML-KEM-1024 contributions, and hybrid-root confirmation; ACCEPTED requires a distinct server confirmation. The provider uses the fixed binary rekey payload below rather than AUTH TLV message types 4 and 5.

Canonical key-schedule input

The kernel validates and encodes, but does not derive a key from, a canonical key-schedule input. The encoding is at most 128 KiB:

u8  schema = 1
u8  reserved = 0
u16 field_count = 18
repeat 18:
    u16 field_id
    u32 value_length
    u8  value[value_length]

The fields are fixed and emitted in this order:

ID Value
1 yume/ytp/1/root/v1
2 exact suite ID
3 initiator role, one octet
4 responder role, one octet
5 32-octet transcript hash
6 32-octet TLS exporter
7 client identity, 1 through 16384 octets
8 server identity, 1 through 16384 octets
9 canonical client capability manifest
10 canonical server capability manifest
11 exact required security parameters
12 32-octet access PSK
13 32-octet client X25519 public key
14 32-octet server X25519 public key
15 32-octet X25519 shared contribution
16 1568-octet ML-KEM-1024 public key
17 1568-octet ML-KEM-1024 ciphertext
18 32-octet ML-KEM shared contribution

The initiator and responder roles MUST each be known and MUST differ. Both capability manifests MUST already be canonical. There are no optional security contributors. The encoder writes into caller-owned output, reports the exact number of octets written, rejects undersized output, and rejects overlap between that output and any input field so secret material is not silently aliased or copied through an unsafe buffer.

The kernel does not provide a decoder or perform cryptography for this encoding. The security provider supplies the checked operations: it requires exact key algorithms and canonical public DER, rejects all-zero hybrid shared contributions, compares authentication values with OpenSSL’s constant-time primitive, and runs the fixed HKDF before publishing peer evidence or directional roots.

Directional rekey

REKEY_INIT and REKEY_ACK frame payloads begin with an engine-visible big-endian u32 next_epoch, followed by a fixed provider message that repeats and cryptographically binds the epoch. The duplicate is intentional: the engine can enforce lifecycle and resource policy without parsing provider private fields, while the provider independently rejects a mismatched epoch.

The 1672-octet INIT provider message is:

u8  schema = 1
u8  kind = 1
u8  direction = initiating sender role (client 1 or server 2)
u8  reserved = 0
u32 next_epoch
u8  ml_kem_1024_public_key[1568]
u8  x25519_public_key[32]
u8  nonce[32]
u8  old_root_hmac_sha256[32]

INIT is a protected YTP record under the old directional epoch. Its HMAC input binds the ratchet domain, exact suite, session/transcript binding, direction, epoch, ML-KEM public key, X25519 public key, and nonce. Acceptance requires the next exact inbound epoch, valid old-root HMAC, nonzero X25519 and ML-KEM contributions, and the fixed message shape.

The 1640-octet ACK provider message is:

u8  schema = 1
u8  kind = 2
u8  direction = original initiating sender role (client 1 or server 2)
u8  reserved = 0
u32 next_epoch
u8  ml_kem_1024_ciphertext[1568]
u8  responder_x25519_public_key[32]
u8  new_root_hmac_sha256[32]

ACK is the bare control-frame exception described above. Its confirmation is computed under the candidate new directional root and binds the ratchet domain, exact suite, direction, epoch, session/transcript binding, the full canonical INIT context excluding its old-root authenticator, ML-KEM ciphertext, and responder X25519 key. The initiator commits its outbound root only after that confirmation succeeds. The responder commits its inbound root only after INIT succeeds. INIT consumes the next old-epoch record sequence and ACK consumes none.

Until the initiator has processed ACK, it MAY keep sending protected records in the old epoch, within that epoch’s limits, each with the next global directional sequence. Its first protected record after ACK uses the new epoch. The responder keeps the old inbound root after accepting INIT and opens old-epoch records with it until the first record of the new epoch opens. It then wipes the old root and MUST treat any later old-epoch record as a protocol failure. INIT MUST travel in the current epoch: an INIT in the epoch it replaces terminates the session. A direction therefore retains at most one old inbound root, and only until the peer’s first record of the new epoch.

At most the configured bounded number of directional rekeys may be in flight. Skipped epochs, repeated INIT or ACK, an ACK without a pending outbound rekey, a protected ACK, a bare non-ACK post-AUTH frame, component mutation, or post-failure reuse terminates the session. Crossed opposite-direction rekeys need no further roots.

A directional epoch carries at most the session’s epoch size in protected payload (the smaller max_epoch_bytes of the two manifests) and at most one record per 2 KiB of it, 512 records for 1 MiB. A receiver MUST treat a peer epoch that exceeds either limit as a protocol failure, counting the old epoch’s records after INIT. The native engine starts a rotation once an epoch has used half its payload, half its records or half its fixed 500 ms send lifetime. It keeps sending in that epoch while REKEY_ACK is on its way and the epoch has room, so a path on which half an epoch lasts longer than a round trip never pauses for the ACK. Once a limit or the 500 ms lifetime is reached, later records wait for the new epoch and keep their order. INIT does not count toward these application thresholds. When the peer’s REKEY_INIT arrives and this direction’s epoch has carried a record and used half its send lifetime, the native engine starts this direction’s rotation right after its REKEY_ACK. A peer that rekeys after a quiet spell is about to send, and the answer then needs no round trip of its own for a new key. This adds no exchange: the answer’s send would have started the same rotation one round trip later. With limits.idle_epoch_rotation, the native endpoint also starts the rotation of an epoch that has carried a record once it is 250 ms old, without waiting for a send. It is off by default: that exchange follows every burst of traffic, which a browser session does not show, and the next send then rotates the epoch itself. Either way an epoch that carried nothing is not rotated, so an idle session’s root does not expire on its own.

A pending outbound rekey has a separate local ACK deadline. The default is 30 seconds; callers may select a positive duration up to 30 seconds. The deadline starts before provider work and INIT queueing. Late traffic or ACK verification fails the session, clears deferred records, and wipes provider state. An engine owner services rekey_deadline() and expire_rekey(now); NativeEndpoint does so with its session timer. Expiry delivery depends on the execution context running. This policy neither changes the wire format nor establishes a high-RTT or delivery-time guarantee.

Canonical vectors

src/ytp/testdata/ytp1_vectors.txt contains public synthetic encoding fixtures, not credentials or cryptographic test keys. The focused executable test checks:

  • frame header 010500000000000100000003;
  • named OPEN 01010000000400006563686f;
  • TCP/DNS OPEN 01010103000a000e6469726563742e74637001bb0b6578616d706c652e636f6d;
  • capability manifest 020000020010000001000004000000086563686f0200000300000004756470;
  • the mandatory-only accepted AUTH encoding recorded in the vector file; and
  • a 3665-octet synthetic key-schedule input whose FNV-1a-64 encoding checksum is e5f96821b0256972.

FNV-1a is used only as an encoding-regression checksum and makes no cryptographic claim.

src/ytp/testdata/ytp1_crypto_vectors.txt records deterministic cryptographic construction vectors. Its independent Python generator uses hashlib, hmac and cryptography AESGCM, with primitive known-answer self-checks. The native security-provider test compares transcript and signature-input hashes, the canonical key schedule, directional roots, PSK and confirmation proofs, record AAD/key/nonce/ciphertext, and both directions of rekey INIT, ACK and new-root derivation. Record cases cover empty plaintext, wide counters and the next epoch with a continuing sequence.

All contributions are public synthetic octet strings. The corpus does not contain provisionable identities or exercise deterministic asymmetric key generation, signatures or encapsulation. Those primitives retain real-key handshake and mutation tests; complete independent peer interoperability and security review remain acceptance gates. The internal providers/ytp1_crypto.* module owns the tested deterministic constructions; the security provider owns authentication state, key lifetimes and ratchets.

Implemented and unfinished boundary

Implemented in the dependency-pure yume_ytp1 static library and exercised by yume_ytp1_kernel_test:

  • stream identifier construction, ownership helpers, and exhaustion checks;
  • exact frame-header and whole-record length validation;
  • bounded OPEN, destination, capability-manifest, and credit codecs;
  • bounded canonical AUTH TLV encoding and decoding;
  • exact suite, security-parameter, width, and domain constants;
  • key-schedule input validation, sizing, and canonical encoding; and
  • canonical encoding vectors and malformed-input tests.

Implemented in the dependency-pure session engine and its in-memory tests:

  • strict AUTH flight ordering, authenticated capability confirmation, and fail-closed provider/suite/exporter checks;
  • the protected-record envelope, exact directional epoch and global sequence, one-use provider-token handoff, and the sole raw REKEY_ACK admission rule;
  • 31-bit odd/even multiplexing, named stream and packet services, destination policy handoff, bounded connection/stream credit, queues, control work, opens, packets, and rekeys; and
  • crossed and sequential directional rekey lifecycle, cancellation, teardown, half-close, backpressure, and allocation-failure contracts.

Implemented in yume_openssl_security_provider and exercised by yume_openssl_security_provider_test with freshly generated real keys:

  • private OpenSSL context and exact Ed25519, ML-DSA-87, X25519, ML-KEM-1024, SHA-256, HMAC, HKDF, and AES-256-GCM algorithm requirements, refusing to construct over a provider that lacks them;
  • canonical unencrypted PKCS#8 private keys, canonical SubjectPublicKeyInfo public keys, per-identity access PSKs, duplicate-identity rejection, exact role factories, and bounded public peer labels;
  • exact three-flight hybrid authentication, transcript/exporter/capability binding, both signature components, PSK precheck, and distinct response and accepted key confirmations;
  • refusal of every AUTH flight with any one field removed or shortened by an octet, or with either signature component stripped, and of each flight replayed into a fresh session or into an established one;
  • directional roots, fresh per-record keys and nonces, exact token ordering, constant-time authentication comparisons, secret wiping, and cancellation; and
  • fixed hybrid rekey INIT/ACK processing, crossed-direction operation, and mutation, mismatch, component-stripping, replay, epoch, and post-failure negative tests.

Implemented as separate native providers with focused tests:

  • yume_tls13_secure_channel wraps an engine ByteChannel with OpenSSL memory BIOs, enforces TLS 1.3 and ALPN h2, verifies client-side trust and hostname, bounds peer evidence, and exports channel binding. Its client applies the shared browser TLS profile and refuses negotiated TLS 1.2 or HTTP/1.1 before publishing a channel. Its separate server-cover entry keeps ordinary TLS 1.2/1.3 H2 traffic in a ByteChannel and rejects HTTP/1.1-only ALPN offers. A no-ALPN connection reaches the front door only to receive the reference’s fixed 239-byte refusal and close, with no H2 state, cover channel or promotion. It promotes only a settled TLS 1.3/H2 connection to the strict SecureChannel type;
  • yume_h2_duplex_carrier implements client priming and exporter-bound admission in extended CONNECT, bounded private record framing, flow-credit ownership, and a typed server promotion seam for an already-admitted live H2 connection;
  • yume_asio_tcp_byte_channel_provider supplies bounded client DNS/connect, socket protection, ordered operations, cancellation, and TCP half-close. AsioTcpAcceptedChannelOwner adopts connected TCP or UNIX stream sockets through the same bounded channel implementation; it does not own a listener; and
  • yume_asio_direct_route_provider supplies bounded TCP and connected-UDP egress behind the dependency-pure route-handler contract, with mandatory numeric-destination policy before socket creation and using the same single-runner execution context and reserved controls as native ingress.

Implemented in yume_h2_web_front_door: a native listener using the accepted-channel owner, a configured static site loaded through confined FileRoot reads, received-SNI/exporter verification, shared replay reservations, bounded admission capacity and one promotion per TLS lifetime. Promotion preserves TLS/H2 state, provider provenance and executor affinity after cover output and timer drain. Ordinary H2 cover handling remains with the carrier. The caller owns execution and completion drain; this ingress performs no DNS.

The internal yume_native_endpoint target composes native ingress, security, session bootstrap and per-identity handlers. An optional route provider enables explicitly authorized destination handlers. Schema-1 direct_tcp and direct_udp declarations create those handlers when the provider is supplied. Their required destinations are enforced before DNS or socket creation, an optional application callback can only refuse more, and resolved-address policy remains mandatory before socket creation. The engine supplies its selected provider to each routed OPEN, and only successful endpoint creation adopts provider cancellation. Other services require explicit bindings. The standalone runtimes compose SOCKS5 and managed Linux packet/TUN declarations. The schema-1 ABI backend exposes named stream/packet and routed TCP/UDP operations, while refusing all adapter declarations. Focused integration exercises configured authenticated TCP/UDP routes, packet boundaries, policy refusal and retained credit after drain, plus real TCP/TLS/H2, AUTH, OPEN refusal followed by acceptance, data, rekey, half-close and final ownership drain. This is an internal runtime seam; its tests do not establish production qualification.

The native endpoint and its providers are internal C++ components. The native executables are installed by default; the experimental C SDK has a separate installation opt-in. Enabling the native programs or the shared ABI builds the complete native provider graph. The relay module, the GUI and other product-specific surfaces are not part of YTP/1.

Published construction vectors cover deterministic transcript, key-schedule, record and rekey calculations. Complete AUTH/rekey interoperability with an independent peer remains open. The status page owns the remaining runtime and release qualification.