HPPR Core Specification

006 – B64A

HPPR-006 · Base64-Ascend (B64A)

B64A is an order-preserving Base64 variant.

Alphabet:

0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz~

Canonical form omits = padding and zero-fills trailing bits.

Alphabet Mapping

~ keeps the alphabet ASCII-sorted and path-safe.

Encoding Rules

Decoding and Validation

Ordering Guarantees

Conformance

Test Vectors

Hex B64A Notes
(empty) empty input
00 00 1 byte
0000 000 2 bytes
000000 0000 3 bytes
FF ~l tail 4 bits zero
FF00 ~l0 tail 2 bits zero
000102 0012 exact 3 bytes

Reject examples: 01, 001, ~m, ~l1, =, +, /.

009 – HSB3

HPPR-009 · HSB3 Signatures

HSB3 is a BIP-340 style Schnorr signature over secp256k1.

HSB3 uses BLAKE3-256 for all hashing and key derivation tags. Reference code lives in rust/libs/hsb3.

Parameters

Helper notation:

Key Generation

  1. Draw random 32-byte d0; reject 0 and d0 >= n.
  2. Set d = d0, compute P = d*G.
  3. If P.y is odd, set d = n - d and recompute P.
  4. Private key is d; public key is bytes(P.x).

The even-y convention is mandatory.

Deterministic Key Derivation

Derive a keypair from arbitrary secret bytes.

  1. Initialize BLAKE3 derive mode with tag hppr-🖧/adhoc-key.
  2. Feed secret bytes.
  3. Enter XOF mode.
  4. Rejection sample 32-byte candidates until 0 < d0 < n.
  5. Apply the same even-y normalization as key generation.

Conformance:

Ring1 Adhoc Derivation (Argon2id)

Ring1 secret tokens derive through Argon2id first.

Token format:

V.<b64a>.H3

Argon2id inputs:

If HELLO omits PHC, defaults are m=12288, t=3, p=1.

Then derive HSB3 key from:

<derived-token>/<ring1>/<repo-vkey>

Signing

Inputs:

Rules:

Algorithm:

  1. t = tagged(aux, auxRand32)
  2. mask = t xor bytes(d)
  3. k0 = tagged(nonce, mask || Px || msg32) mod n; reject k0 = 0
  4. R = k0*G; if R.y odd then k = n-k0, else k = k0
  5. e = tagged(challenge, bytes(R.x) || Px || msg32) mod n
  6. s = (k + e*d) mod n
  7. signature bytes are bytes(R.x) || bytes(s)

Text form uses B64A.

Verification

Inputs: signature r||s, public key Px, and msg32.

  1. Parse r, s; reject r >= p or s >= n.
  2. Recover P from Px and take even-y root.
  3. e = tagged(challenge, bytes(r) || Px || msg32) mod n
  4. R' = s*G - e*P; reject infinity or odd y.
  5. Accept iff R'.x == r.

Conformance

Implementations:

HPPR Packet Format

© R.A.Sol

Every packet starts with a markline:

🖧: <hash>

<hash> is BLAKE3-256 in B64A. It identifies the canonical payload after the first LF.

HPPR packet types:

Shared Rules

Blob

🖧: <hash>
Data-Length: <len>

<data exactly <len> bytes>

Rules:

Plex

🖧: <hash>
Group: <group>
App: <app>
Location: <location>
TAI: <tai>
[Extra headers]*
🖧: <blob_hash>
Data-Length: <len>

<data>

Rules:

TAI Format

TAI format:

<seconds>:<subseconds>

Rules:

TAI is currently UTC +37 seconds (at time of writing, 2026).

Header Validation

General:

Group and App:

Location:

Extra Headers

Extra-header ordering is canonical:

Reserved header names (MUST NOT appear as extra headers):

Limits:

Seal

🖧: <hash>
Seal-By: <verification-key>
Seal-Sig: <signature>
🖧: <plex_hash>
Group: <group>
App: <app>
Location: <location>
TAI: <tai>
[Extra headers (sorted)]*
🖧: <blob_hash>
Data-Length: <len>

<data>

Rules:

Limits

Parsing

  1. Read markline and verify the type prefix in the hash.
  2. Blob:
    • read Data-Length
    • consume exact data bytes
    • verify Blob hash
  3. Plex:
    • read required headers in required order
    • read extra headers with canonical ordering checks
    • parse embedded Blob
    • verify Plex hash
  4. Seal:
    • read Seal-By, Seal-Sig
    • parse embedded Plex
    • verify Seal hash
    • verify signature

Reject on any of the following:

Trailer-hash Format

Trailer-hash format supports streaming when the final hash is only known at the end.

Open marker:

⋯🖧: <type>[ <coordinate>]

Close marker:

⋯🖧: <hash>

Rules:

Seal trailer example:

⋯🖧: S //<group>/<app>/<location>/|/seal/<verification-key>
<escaped data>
⋯🖧: B.<blob-hash>.H3
Group: <group>
App: <app>
Location: <location>
TAI: <tai>
[Extra headers]*
⋯🖧: P.<plex-hash>.H3
Seal-By: <verification-key>
Seal-Sig: <signature>
⋯🖧: S.<seal-hash>.H3

Additional rules:

Both sides buffer full packet bytes to compute and verify hashes.

Thin Format

Thin format stores only part of a packet.

Thin packets omit embedded content bytes and rely on nested hashes for reconstruction.

Hash computation always requires full packet bytes. Implementations MUST resolve thin references before hash verification.

Thin format is for storage and transfer when inner packets are already present.

011 – Basic Example

HPPR Basic Packet Examples

This file shows one Blob, one Plex, and one Seal packet in readable form. Hashes are shortened for line-width clarity.

Blob Example

🖧: B.EXAMPLE_BLOB_HASH.H3
Data-Length: 34

# HPPR Quickstart
This is Blob data.

Plex Example

🖧: P.EXAMPLE_PLEX_HASH.H3
Group: a-group
App: some-app
Location: our-collection/item
TAI: 1640995200:000000000
+Link: source B.EXAMPLE_BLOB_HASH.H3
X-Custom: header value
🖧: B.EXAMPLE_BLOB_HASH.H3
Data-Length: 25

This is embedded blob data.

Seal Example

🖧: S.EXAMPLE_SEAL_HASH.H3
Seal-By: V.EXAMPLE_VKEY.H3
Seal-Sig: EXAMPLE_SIGNATURE_B64A
🖧: P.EXAMPLE_PLEX_HASH.H3
Group: a-group
App: some-app
Location: our-collection/item
TAI: 1640995200:000000000
🖧: B.EXAMPLE_BLOB_HASH.H3
Data-Length: 22

This is signed content.
012 – Create Examples

Reference Script for 011 Examples

This script generates the examples used in 011-PACKETS-BASIC-EXAMPLE.md.

#!/bin/bash -e

blob_msg="# HPPR Quickstart

HPPR packets are content-addressed using BLAKE3.
This Blob contains raw markdown data."

plex_msg="# Plex
This is a plex packet with metadata and links."

seal_msg="# Seal
This is a signed packet that wraps a plex packet."

# Deterministic test-only signatures.
export _UNSAFE_VULNERABLE_HPP_HSB3_DETERMINISTIC_AUX_SEED="examples"
export HPPR_SECRET_KEY="&.ydejWAbshBxyrcKILG3bXkD7fU5c72LtHvLJRfzGXal.H3"
tai="1640995200:000000000"

out="./spec/011-PACKETS-BASIC-EXAMPLE.md"

# Blob
echo -n "$blob_msg" | mkpac --blob > "$out"
blob_hash=$(head -n 1 "$out" | awk '{print $2}')

# Plex
echo -n "$plex_msg" | mkpac \
  -g a-group \
  -a some-app \
  -l our-collection/item \
  -t "$tai" \
  -H "+Link: source $blob_hash" \
  -H "X-Custom: header value" \
  -H "Multiple-Values: B" \
  -H "Multiple-Values: A" \
  >> "$out"

# Seal
echo -n "$seal_msg" | mkpac \
  -k "$HPPR_SECRET_KEY" \
  -g a-group \
  -a some-app \
  -l our-collection/item \
  -t "$tai" \
  >> "$out"

Unified Resource Coordinate

© R.A.Sol

HPPR supports two address forms:

Hash addresses are immutable. Coordinate addresses are time-dependent and may resolve to different packets as new packets are stored.

A versioned coordinate pins one exact packet.

Terms

Version Selectors

Multiple packets can share one coordinate. Add /|/... to select a version.

Example

Packet:

🖧: P.EXAMPLE~HASH~EXAMPLE~HASH~EXAMPLE~HASH~EX.H3
Group: a-group
App: some-app
Location: our-collection/item
TAI: 1640995200:123000000
...

Addresses:

Get resolution

In the tables below, <loc> is the Location header value and does not end with /.

The tip is the latest packet at a path. Tie-break order is highest TAI, then highest hash.

Path Gets
.../<loc> tip
.../<loc>/ tip
.../<loc>/| tip
.../<loc>/|/plex Plex tip
.../<loc>/|/plex/<tai> Plex tip at tai
.../<loc>/|/plex/<tai>/<hash> exact Plex
.../<loc>/|/seal Seal tip
.../<loc>/|/seal/<vkey> Seal tip for signer
.../<loc>/|/seal/<vkey>/<tai> signer Seal tip at tai
.../<loc>/|/seal/<vkey>/<tai>/<hash> exact Seal

List resolution

Path Lists
.../<loc>/ child locations, plus |/ when present
.../<loc>/|/ plex/ and or seal/
.../<loc>/|/plex/ TAIs
.../<loc>/|/plex/<tai>/ hashes
.../<loc>/|/seal/ signer verification keys
.../<loc>/|/seal/<vkey>/ TAIs
.../<loc>/|/seal/<vkey>/<tai>/ hashes
022 – Filesystem

Filesystem Repository

© R.A.Sol

This spec defines filesystem storage for HPPR packets.

It follows packet rules in 010 and address rules in 020.

Repository Layout

repo/
├── hash/<Type>/<hh>/<tail>.H3
├── ref/B/<hh>/<tail>/...
├── ref/P/<hh>/<tail>/...
├── index/<group>/<app>/...
├── detach/<hash>
└── .tmp/

Filesystem requirements:

Hash Storage (hash/)

Path form:

hash/<T>/<hh>/<tail>.H3

Where:

Storage format:

Blob file size defines Data-Length during reconstruction.

Index Storage (index/)

Plex path:

index/<group>/<app>/<loc>/|/plex/<tai>/<hash>

Seal path:

index/<group>/<app>/<loc>/|/seal/<vkey>/<tai>/<hash>

Rules:

Back-Reference Storage (ref/)

Blob to Plex references:

ref/B/<hh>/<tail>/<plex-hash>

Plex to Seal references:

ref/P/<hh>/<tail>/<seal-hash>/<vkey>

These support reverse traversal from inner to outer packet.

Store Flow

  1. Write Blob raw bytes to hash/B/....
  2. For Plex:
    • write thin Plex to hash/P/...
    • add index/.../|/plex/<tai>/<hash>
    • add ref/B/.../<plex-hash>
  3. For Seal:
    • write thin Seal to hash/S/...
    • ensure embedded Plex and Blob exist
    • add index/.../|/seal/<vkey>/<tai>/<seal-hash>
    • add embedded Plex index entry
    • add ref/B/.../<plex-hash> and ref/P/.../<seal-hash>/<vkey>

Duplicates SHOULD succeed as idempotent writes. Use .tmp/ plus atomic rename for final placement.

Detach Cleanup

When detaching Plex or Seal:

  1. remove index entries
  2. remove matching ref/B/.../<plex-hash>
  3. if that ref/B/... directory becomes empty, record blob hash in detach/
  4. never detach the empty-blob constant hash

Reconstruction

Hash verification requires full reconstructed packet bytes.

Tip Tracking

Tips are materialized as links under index/.../|/.

Required tip links:

Tip update rule:

Tip Recovery

If a tip link is missing while version directories exist, implementation MUST:

  1. scan affected |/plex/ and |/seal/ once
  2. recompute latest entries
  3. recreate missing tips

If scan finds no packets, empty |/ structure SHOULD be cleaned up.

After detach, recompute tip links for the affected coordinate.

Portability

HPPR Basic Commands

© R.A.Sol

Security Model

HPPR validates packets at the packet layer, not the channel layer.

Non-stream commands use one request packet and one response packet per exchange. Streaming behavior is command-specific (for example 🖧WATCH, 🖧AUDIT in 050).

Null Packet

The sentinel hash 0.H3 is never computed or verified.

Use 0.H3 for:

Null packets are never stored in repository storage.

🖧: 0.H3
[Header: value]*
Data-Length: <len>

<data>

Header rules follow 010 (format, encoding, control bytes, line length). Differences from Plex:

Session Lifecycle

Each connection starts with HELLO.

The repo creates a session-id and binds it to the connection. Later requests must carry that session-id in Location.

HELLO Request

Client sends a Null packet with App: 🖧HELLO:

🖧: 0.H3
App: 🖧HELLO
Data-Length: 0

HELLO Response

Repo responds with session parameters and capabilities:

🖧: 0.H3
Session-ID: <session-id>
Repo-Name: <repo-name>
Seal-By: <repo-vkey>
[PHC: $argon2id$v=19$m=<m>,t=<t>,p=<p>$]
[Command: 🖧NAME VERSION]*
[Transport: <via>]*
Data-Length: 0

Header meanings:

Header Meaning
Session-ID Repo-generated TAI for this connection session
Repo-Name Repository identifier. Default is localhost
Seal-By Repo verification key from ring0 keys, oldest by TAI/hash
PHC Argon2id parameters for Ring1 token derivation
Command Supported command and integer version
Transport Available transport via string (031)

Sealed HELLO

After session setup, an authenticated 🖧HELLO Seal request returns fresh status and capabilities. The response is a Seal signed by repo-vkey. Session-ID and PHC are omitted.

No ACL check. Any authenticated identity may send 🖧HELLO.

Repo Identity

Repo identity is stored at:

//repo/admin/identity/|

It is a self-signed Seal. Repo-Name in that identity is returned in HELLO.

Authenticated Requests

After HELLO, commands are sent as Seal packets. App values starting with 🖧 are protocol commands.

🖧: S.<hash>.H3
Seal-By: <client-vkey>
Seal-Sig: <signature>
🖧: P.<hash>.H3
Group: <group>
App: 🖧<COMMAND>
Location: <location-with-session>
TAI: <tai>
🖧: B.<hash>.H3
Data-Length: <len>

<args>

Location format depends on auth scheme:

Scheme Group Location
Ring1 (050) repo <repo>/<ring1>/<session-id>
Ring2 (060) <target-group> <repo>/<session-id>
Anyone repo <repo>/anyone/<session-id>

A different signing key requires a new connection and HELLO.

Command Responses

Successful responses are Seal packets signed by the repo verification key.

🖧: S.<hash>.H3
Seal-By: <repo-vkey>
Seal-Sig: <signature>
🖧: P.<hash>.H3
Group: repo
App: 🖧<COMMAND>
Location: <repo-name>/<session-id>
TAI: <response-tai>
🖧: B.<hash>.H3
Data-Length: <len>

<response-data>

Commands

Command Payload Response ACL
🖧HELLO (none) session parameters (Null or Seal)
🖧GET <urc> (020) full packet bytes read
🖧HEADERS <urc> markline + headers to first blank line read
🖧LIST <urc-coordinate> LF-separated children, sorted list
🖧STORE complete packet bytes LF-separated stored hashes write

🖧STORE

🖧STORE payload is a complete packet starting with markline.

It also accepts thin packets:

For authenticated App: 🖧STORE, request Data-Length may be up to 34 MiB. This allows 32 MiB packet payload plus up to 2 MiB envelope overhead.

Other authenticated Seal apps remain capped at 32 MiB.

Response data lists stored hashes from outermost to innermost:

S.seal~hash.H3
P.plex~hash.H3
B.blob~hash.H3

Errors

Errors are Null packets. Data starts with one status line:

Standard error types:

Example:

🖧: 0.H3
Data-Length: 21

ERROR NOT_FOUND ring1
031 – Via Syntax

Via Syntax

© R.A.Sol

A via string identifies a transport endpoint.

Grammar

Full form:

scheme+host[:port]

Bare form (auto-negotiate transport):

host[:port]

Hostless form (used in HELLO Transport headers where host is already known):

scheme[:port]

Unix domain socket:

unix+<absolute-path>

Schemes

Scheme Default port Transport
tcp 4777 TCP plaintext
quib 4776 QUIB encrypted (033)
ws 4778 WebSocket
udp 4777 UDP datagram (stateless)
auto 4777 Auto-negotiate (equivalent to bare form)

Host

Host is a hostname, IPv4 address, or bracketed IPv6 address.

IPv6 bracket form: [::1] or [::1]:port.

Port

Port is optional. When omitted, the scheme default applies. Bare form defaults to 4777.

Hostless Resolution

Hostless forms appear in HELLO Transport headers. The client fills in the host from its existing connection. Examples:

Display Form

The canonical display form is:

Examples

Input Scheme Host Port
tcp+10.0.0.1 tcp 10.0.0.1 4777
tcp+10.0.0.1:9000 tcp 10.0.0.1 9000
quib+1.1.1.1:4776 quib 1.1.1.1 4776
udp+10.0.0.1 udp 10.0.0.1 4777
ws+example.com ws example.com 4778
auto+example.com auto example.com 4777
example.com auto example.com 4777
example.com:9000 auto example.com 9000
tcp:4777 tcp (from context) 4777
udp udp (from context) 4777
unix+/tmp/hppr.sock
032 – UDP

UDP Datagram Transport

© R.A.Sol

Stateless single-datagram transport for fast lookups.

Via: udp+host[:port] (031). Default port: 4777.

Advertised in HELLO Transport headers as udp[:port].

Supported Commands

Protocol

No HELLO, no session. Each exchange is one request datagram and one response datagram.

Request is a stateless sealed request (035) in a single UDP datagram.

Response is one datagram: raw packet bytes on success, Null packet on error. Unrecoverable failures produce no response.

033 – QUIB

QUIB

© R.A.Sol

HPPR QUIB transport uses a custom crypto layer instead of TLS 1.3.

Primitives:

No TLS, no X.509, no rust ring. Both peers must run this crypto.

Handshake

Two-message exchange inside QUIC Initial CRYPTO frames. Both messages travel in the Initial encryption space. After ECDH completes, each side writes a single 0x00 confirmation byte into Handshake CRYPTO to signal readiness, then upgrades to Data-space keys.

Message 1 (client to server, Initial CRYPTO)

client_ephemeral_pubkey (33 bytes, SEC1 compressed secp256k1)
client_transport_parameters (variable)

Message 2 (server to client, Initial CRYPTO)

server_ephemeral_pubkey (33 bytes, SEC1 compressed secp256k1)
encrypted_hello_length (2 bytes, big-endian u16)
encrypted_hello (variable, ChaCha20-Poly1305)
server_transport_parameters (variable)

The entire Message 2 is sent in a single CRYPTO frame. The client derives the shared secret from the server’s ephemeral pubkey, then decrypts the hello and parses transport parameters in one step.

Transport parameters use quinn-proto’s wire encoding (TransportParameters::write / TransportParameters::read).

Encrypted HELLO Payload

The server encrypts a HELLO payload into Message 2 using hello_key from the key derivation XOF stream (offset 256, see Key Derivation).

Encryption uses ChaCha20-Poly1305 with a zero nonce (unique key per connection).

Plaintext is a standard Null HELLO packet (per 030-BASIC-COMMANDS.md) without Session-ID. Contains:

When available, Transport headers advertise other active transports.

The encrypted hello includes a 16-byte Poly1305 tag appended to the ciphertext.

Post-handshake stream bootstrap

QUIB keeps standard QUIC endpoint roles:

All application streams are client-initiated. The HELLO payload is delivered during the handshake, so no server-initiated stream is needed.

  1. client reads repo metadata from handshake_data() (the decrypted HELLO)
  2. client derives Session-ID from keying material
  3. client opens a bidirectional stream and sends commands
  4. server accepts streams and dispatches commands

The server uses accept_bi() for all streams. The client uses open_bi() for the command stream and any additional streams.

Session-ID Derivation

Session-ID is session_id from the key derivation XOF stream (offset 288, see Key Derivation).

Output is 32 bytes. Text format is Q#<b64a> (the Q# prefix distinguishes QUIB-derived session-ids from TAI-based ones).

Both sides compute the same value. This replaces the server-generated TAI session-id used on TCP connections.

The 🖧HELLO command remains available as an optional application-level request for capabilities refresh, repo-name lookup, or sealed status queries.

Key Agreement

shared_point = ECDH(client_ephemeral_secret, server_ephemeral_pubkey)
shared_bytes = shared_point.x (32 bytes, big-endian)

Both sides generate fresh ephemeral secp256k1 keypairs per connection.

Key Derivation

All keys for a connection are derived from the ECDH shared secret using a single BLAKE3 XOF stream:

stream = BLAKE3.derive_key_xof("hppr-🖧/quib/keys", shared_bytes)

Stream layout (368 bytes):

Offset Length Name
0 32 hs_c2s_packet
32 32 hs_s2c_packet
64 32 hs_c2s_header
96 32 hs_s2c_header
128 32 c2s_packet
160 32 s2c_packet
192 32 c2s_header
224 32 s2c_header
256 32 hello_key
288 32 session_id
320 12 hs_c2s_iv
332 12 hs_s2c_iv
344 12 c2s_iv
356 12 s2c_iv

Handshake keys (hs_*) protect the Handshake packet space. Data keys protect the 1-RTT (Data) packet space.

Each packet key has a sibling IV derived from the same XOF stream. The IV is used in nonce construction (see Payload Encryption below).

quinn-proto requires two key upgrades during handshake: Initial to Handshake, then Handshake to Data. Both sides derive keys from this layout and return them via write_handshake. Each side writes a 0x00 confirmation byte into Handshake CRYPTO when returning 1-RTT keys, ensuring the peer receives a Handshake-space packet and completes the transition to Data.

QUIC Key Update

Each direction derives 44 bytes via BLAKE3 XOF from the current packet key:

stream = BLAKE3.derive_key_xof("hppr-🖧/quib/update", current_packet_key)
next_packet_key = stream[0..32]
next_iv          = stream[32..44]

Both key and IV are replaced. Header keys are not updated.

QUIC Payload Encryption

ChaCha20-Poly1305 (RFC 8439).

Nonce construction (12 bytes), following the TLS 1.3 pattern (RFC 9001 §5.3):

nonce = IV XOR (0x00000000 || BE64(packet_number))

The IV is the 12-byte sibling value derived alongside the packet key (from the XOF stream for initial/handshake/data keys, or from the key update XOF for rotated keys). The packet number is encoded as an 8-byte big-endian integer and XORed into the last 8 bytes of the IV.

Tag length: 16 bytes, appended to ciphertext.

Limits:

QUIC Header Protection

ChaCha20 mask generation per RFC 9001 §5.4.4.

Input: 16-byte sample from encrypted payload at offset pn_offset + 4.

counter = LE_u32(sample[0..4])
nonce   = sample[4..16]
mask    = ChaCha20(hp_key, counter, nonce, zeroes)[0..5]

Apply mask:

Sample size: 16 bytes.

Initial QUIC Keys

Before handshake completes, both peers derive identical keys from the destination connection ID using a single BLAKE3 XOF stream:

stream = BLAKE3.derive_key_xof("hppr-🖧/quib/initial", dst_cid)

Stream layout (152 bytes):

Offset Length Name
0 32 c2s_packet
32 32 s2c_packet
64 32 c2s_header
96 32 s2c_header
128 12 c2s_iv
140 12 s2c_iv

Initial keys use the same ChaCha20-Poly1305 and ChaCha20 algorithms as 1-RTT keys. Initial protection is not secret (the CID is on the wire).

Retry Integrity

Retry tags use BLAKE3 keyed MAC with a fixed public key, truncated to 16 bytes.

tag = BLAKE3.keyed_hash(RETRY_KEY, len(orig_dst_cid) || orig_dst_cid || retry_pseudo_packet)[..16]

RETRY_KEY is a fixed 32-byte constant compiled into both peers:

8a 3f c1 7b 52 e6 d9 04  ab 1e 73 f0 28 95 dc 46
b3 67 0a 5d e4 89 f1 3c  7e b2 05 6f d8 a1 43 97

This mirrors RFC 9001’s public retry integrity key. The tag is tamper-detection, not a secret.

HMAC

BLAKE3 keyed hash with 32-byte output.

mac = BLAKE3.keyed_hash(key, data)

Verification compares all 32 bytes.

Token Encryption

Address validation tokens use per-token derived ChaCha20-Poly1305.

Key derivation:

aead_key = BLAKE3.derive_key("hppr-🖧/quib/token-aead", master_key || random_bytes)

Seal and open use a zero nonce. Each token has a unique derived key, making nonce reuse impossible.

Export Keying Material

output = BLAKE3.derive_key(label, shared_bytes || context)

label is interpreted as a UTF-8 string for the BLAKE3 context parameter. Output length is variable via BLAKE3 XOF.

Peer Identity

peer_identity returns the peer’s ephemeral SEC1 compressed secp256k1 public key (33 bytes).

handshake_data returns the decrypted HELLO payload bytes (client side) or the client ephemeral public key (server side).

For clients, the HELLO payload is a standard Null packet containing the server’s repo-vkey and capabilities.

No 0-RTT

Early data is not supported. early_crypto returns None.

Context Strings

All BLAKE3 derive_key / XOF context strings used by this spec:

Context Use
hppr-🖧/quib/keys XOF for all handshake/data keys, IVs, hello key, session-id
hppr-🖧/quib/initial XOF for initial keys and IVs
hppr-🖧/quib/update XOF for key update (packet key + IV per direction)
hppr-🖧/quib/token-aead Token AEAD key derivation
035 – Stateless

Stateless Requests

© R.A.Sol

Stateless requests skip the HELLO handshake. A client sends a Seal before HELLO using Location: <via>/anyone/stateless.

<via> is the via string (031) for the transport the client connected to (e.g. tcp+1.1.1.1:4777). The server does not validate this segment for stateless requests.

Rules

Stateless requests are replayable by design. They provide packet integrity via the Seal signature, not identity. Use only for public read-only data.

UDP

UDP uses stateless requests exclusively. See 032.

HPPR Access Control

© R.A.Sol

ACL rules are defined per repository identity.

Each rule targets a coordinate prefix and controls three operations:

Rule evaluation uses longest-prefix match with per-operation inheritance.

Operations

Rule Format

Rule syntax:

<ops> <coordinate-prefix>

<ops> is exactly three characters:

[r|d|.][w|d|.][l|d|.]

Meaning:

Examples:

ACL-Rule: rwl //u/chess/
ACL-Rule: r.l //u/mail/
ACL-Rule: rdl //u/market/
ACL-Rule: .w. //u/market/nl/eindhoven/

Common patterns:

Rule Ordering

Rules MUST be stored in canonical sorted order.

Sort bytewise, with custom priority:

Equivalent compare mapping:

Resolution

Given a request coordinate:

  1. Find the longest matching rule prefix.
  2. For each operation, apply explicit allow or deny when present.
  3. For . values, continue to the next-longest matching rule.
  4. If no explicit decision is found, deny.

For read and write checks, evaluate against the packet versioned coordinate.

Prefix examples:

Prefix Matches
//u/a/README.md/| exact README.md location
//u/a/README.md/ README.md and children
//u/a/README.md also README.md-draft

Identity Storage

Rules are stored as ACL-Rule Plex headers.

Location depends on identity scheme:

HPPR Ring1 Authentication

© R.A.Sol

Ring1 is the standard repository-auth API. All Ring1 requests are Seals signed by a Ring1 member key.

The key can be derived from a password or set up explicitly.

Request Envelope

Ring1 request form:

🖧: S.<hash>.H3
Seal-By: <member-vkey>
Seal-Sig: <signature>
🖧: P.<hash>.H3
Group: repo
App: 🖧<COMMAND>
Location: <repo-name>/<ring1-name>/<session-id>
TAI: <tai>
🖧: B.<hash>.H3
Data-Length: <len>

<args>

Rules:

Keys

ring0 Key

The repo verification key is the oldest packet at:

//repo/admin/ring1/ring0/keys/|/seal

Oldest means lowest (TAI, hash).

Ring1 and Ring2 setup packets must be signed by this repo key.

Ring1 Keys Config

Path:

//repo/admin/ring1/<name>/keys/|/seal/<vkey>

Packet contains Secret-Key: &.<b64a>.H3. 🖧ADD can use these keys.

Setup Config

Path:

//repo/admin/ring1/<name>/setup/|

Signed by ring0.

Headers:

Ring1-Name constraints:

Secret Token Derivation

Ring1-Secret-Token format:

<derived-token> <original-secret>

Split on first ASCII space.

Client derives <derived-token> using Argon2id with HELLO PHC. Repo does not recompute Argon2id. Repo uses <derived-token> directly.

Argon2id:

Then HSB3 key derivation secret is:

<derived-token>/<ring1-name>/<repo-vkey>

Special Ring1 Names

Pre-ACL Defaults

These apply before ACL-Rule evaluation and are final.

Ring1 Commands

Ring1 includes all commands from 030 and adds:

🖧ADD

Input is LF-separated headers, blank line, optional data.

Type selection:

Defaults:

References:

🖧DETACH

Payload is one hash. Removes packet from coordinate index only. Stored packet remains in hash storage. ring0 only.

🖧TIPS

Returns LF-separated versioned coordinates for tip packets.

🖧WATCH

Returns a stream of + and - lines for matching coordinate prefix changes. Events are filtered by list permission.

🖧AUDIT

Streams audit log lines. ring0 only.

Errors

Common Ring1 failures:

Security

Ring1 expects encrypted transport for confidentiality. Replay resistance uses HELLO-bound session IDs in request Location.

HPPR Ring2 Authentication

© R.A.Sol

Ring2 is group-based authentication. Signer authorization is based on membership in the target group.

Request Envelope

Ring2 request form:

🖧: S.<hash>.H3
Seal-By: <member-vkey>
Seal-Sig: <signature>
🖧: P.<hash>.H3
Group: <target-group>
App: 🖧<COMMAND>
Location: <repo-name>/<session-id>
TAI: <tai>
🖧: B.<hash>.H3
Data-Length: <len>

<args>

Rules:

Group Setup

Setup path:

//<group>/admin/setup/|/seal/<repo-vkey>

Setup packet is signed by repo-vkey.

Headers:

Each ACL-Rule coordinate must start with //<group>/.

Pre-ACL Default

Before ACL rules, apply:

This grants read access to membership config.

Membership Config

Membership packets live at:

//<group>/admin/members/|/seal/<vkey>

Multiple packets may exist under different signers and versions.

Supported headers:

Member

Adds one member key plus optional tags.

Member-Delegate

Delegates membership from another config source. Pipe separator | is required.

Defaults:

Pinned form:

Modifier:

Tag modifiers:

Traversal is depth-first with max depth 8.

🖧MEMBERS

Returns expanded member list with tags.

Payload is a // URC.

Shorthand:

Response:

Errors

Common Ring2 failures:

HPPR Conventions

© R.A.Sol

This spec defines bootstrap and onboarding conventions.

Bootstrap Requirements

A new repository needs four initial items.

  1. ring0 keys
    • path: //repo/admin/ring1/ring0/keys/|/seal/<vkey>
    • self-signed Seal with Secret-Key
    • oldest key becomes repo-vkey
  2. ring0 setup
    • path: //repo/admin/ring1/ring0/setup/|/seal/<repo-vkey>
  3. anyone setup
    • path: //repo/admin/ring1/anyone/setup/|/seal/<repo-vkey>
  4. guest setup
    • path: //repo/admin/ring1/guest/setup/|/seal/<repo-vkey>

ring0 Initial Token

Initial ring0 member derives from token init:

secret = "init/ring0/<repo-vkey>"

signing_key = derive_key_from_secret(secret)

Replace bootstrap setup immediately with a secure token.

Default anyone Setup

Ring1-Name: anyone
ACL-Rule: .w. //repo/admin/request/ring1/
ACL-Rule: r.l //repo/admin/route/
ACL-Rule: r.l //u/

Default guest Setup

Ring1-Name: guest

Joining a Repository

Standard Ring1 join flow:

  1. user writes request to: //repo/admin/request/ring1/<name>/setup/|
  2. user watches reply path: //repo/admin/request/ring1/<name>/reply/|
  3. admin approves or denies in reply packet
  4. if approved, admin creates ring1 setup packet
  5. user derives key and reconnects

Provisional Access

An anyone request gets temporary read/list access to matching reply path:

//repo/admin/request/ring1/<name>/reply/|

<name> must match first segment from request Location.

Joining a Group

Ring2 join flow:

Recommended guest ACLs:

ACL-Rule: .w. //<group>/admin/request/member/|/seal/
ACL-Rule: r.l //<group>/admin/request/member/

Request headers:

Reply headers:

Group Deployment Pointer Convention

Canonical app content should live under:

//u/apps/<publisher>/<app>/...

Each group publishes its deployment pointer at:

//<group>/admin/deploy/<app>/|

Required headers:

Runtime intent:

Group forks are supported by pointing Deploy-Root to a group-owned subtree instead of the canonical //u/apps/... tree.

Generic form:

+Link: <tag> <hash>

Common tags:

Typed links are headers named [Type]+Link. Tools should scan header names containing +Link.

Hash links form a DAG. Cycles are impossible because packet hash includes all header bytes.

Chunked Content Convention

Large content can be split into chunk blobs with a manifest packet.

Manifest detection:

Syntax:

Chunk+Link: <start>..<end> <T>.<hash>.H3

Required header:

Optional headers:

Validation:

Nested manifests use 0-relative ranges in each sub-manifest. Depth limit is 8.

Default chunk size is 32 MiB. Smaller chunks improve random access and increase overhead.

Ring0-Proxy Convention

Ring1 may request a ring0-mediated action.

Request path:

//repo/admin/ring1/<ring1-name>/🖧<COMMAND>/|

Reply path:

//repo/admin/ring1/<ring1-name>/🖧<COMMAND>/reply/|

Supported commands:

🖧GET is excluded. Use 🖧HEADERS to obtain hash, then fetch blob by hash.

HPPR Replication

© R.A.Sol

Repository replication uses command 🖧EXCHANGE. It synchronizes packets while preserving ACL decisions.

🖧EXCHANGE

🖧EXCHANGE has three phases:

  1. announce
  2. transfer
  3. finalize

Phase 1: Announce

Requester sends up to 1024 LF-separated lines:

NEED <versioned-coord>
HAVE <versioned-coord>

Responder returns FIN or PENDING followed by status lines:

SEND <coord>
DENY <coord>
RECV <coord>
HAVE <coord>

Status meanings:

Phase 2: Transfer

Both peers stream raw packets concurrently in both directions.

Implementations MUST perform bidirectional I/O concurrently. Unidirectional blocking can deadlock the exchange.

Packet boundaries are parsed using Data-Length.

Phase 3: Finalize

Responder reports final per-coordinate results:

FIN
OK <coord>
FAIL <coord> <type> <msg>

ACL Rules in Phase 1

For NEED <coord>:

  1. check read
  2. if readable and exists: SEND
  3. else: DENY

For HAVE <coord>:

  1. check read
  2. if denied: DENY
  3. if allowed and exists: HAVE
  4. if missing, check write
  5. if writable: RECV
  6. else: DENY

Blob-Addressed 🖧EXCHANGE

🖧EXCHANGE also accepts blob hash addressing:

NEED ////B.<hash>.H3
HAVE ////B.<hash>.H3

Rules:

Decision rules:

Transfer still uses full raw blob packets.

🖧STREAM_IN

🖧STREAM_IN ingests live trailer-format Seal segments.

Request:

Response:

After OK, connection enters relay mode and accepts trailer segments.

Per segment:

  1. relay bytes to matching 🖧STREAM_OUT subscribers
  2. detect segment close marker
  3. verify hash and signature
  4. store packet
  5. emit 🖧WATCH event

ACL: write checked per segment at store time.

Disconnect behavior:

No FATAL is injected into relay stream. Subscribers detect truncation via trailer parsing.

Size rule:

Continuity recommendation:

🖧STREAM_OUT

🖧STREAM_OUT subscribes to live trailer bytes by prefix.

Request:

Behavior:

Late joiners receive bytes buffered from current in-flight segment and then continue live.

ACL: read checked at subscription time.

On publisher disconnect, repo closes subscriber sockets. Mid-segment disconnect yields partial trailer data followed by EOF.

Completed segments remain available via 🖧GET and 🖧EXCHANGE.

Relay Chaining

A downstream repo can relay a live stream by:

  1. subscribing upstream with 🖧STREAM_OUT
  2. publishing downstream with 🖧STREAM_IN

  1. .H3 names the active format extension: BLAKE3-256 hashing, HSB3 signatures, UTF-8 NFC normalization (Unicode 17.0.0), and this packet structure.↩︎