Blind-Signed Vouchers and Anonymous Access Credentials
A source-verified guide to AeroNyx VPN blind vouchers and Blind Vault RFC 9474 admission, including rollout, replay, issuer, and privacy boundaries.
AeroNyx uses blind-signature techniques in two distinct authorization paths. Both reduce identity linkage, but they have different rollout and replay guarantees. This page states what Rust main implements, what remains deployment-controlled, and what operators must never infer from a credential.
Two credential paths
The VPN path authorizes a ClientHello using a finalized blind-signature credential. Blind Vault V2 uses an RFC 9474 credential to create a random, self-authenticating encrypted-storage lease. V1 Blind Vault admission remains a linkable one-time bearer compatibility path and is not blind issuance.
| Credential path | Current state | Replay/redemption model |
|---|---|---|
| VPN ClientHello voucher | Implemented; reject_invalid compatibility rollout | No one-time spend in VoucherVerifier |
| Blind Vault V2 admission | Implemented in Rust; deployment/config controlled | Atomic one-time spend plus idempotent exact retry |
| Blind Vault V1 admission | Compatibility only; prefer V2 for new integrations | Atomic one-time spend, but issuance is linkable |
Privacy invariant
The privacy goal is authorization without exposing the issuance identity to the redeeming node. The signer, entitlement backend, storage node, and operator console must remain separate trust and data boundaries; combining their request-level records would defeat unlinkability.
- A redeeming node validates service rights without receiving wallet/account identity.
- The blind signer receives blinded bytes and a public key ID, never entitlement identity.
- Credential secrets, tokens, randomizers, spend IDs, lease IDs, and private keys never enter logs or Nodeboard.
- Synthetic or aggregate counters cannot be joined with per-user traffic.
- Wallet blind-signing warnings are unrelated: users are not approving an unread transaction.
VPN handshake voucher
The client sends a bounded trailing AVCH extension. Rust parses only the credential fields, downloads public issuer keys by epoch from the configured issuer directory, caches them for one hour, and verifies a randomized SHA-384 RSA-PSS blind signature. It does not need a wallet or account identifier.
ClientHello fixed frame
+ magic: AVCH
+ voucher_length: u16 little-endian
+ voucher JSON (maximum 2048 bytes)
{
"token": "base64-final-credential-message",
"signature": "base64-finalized-blind-signature",
"msg_randomizer": "base64-32-byte-randomizer",
"epoch": "issuer-key-epoch"
}
The issuer-side flow is blind only when the client blinds the message before the entitlement service asks the signing boundary to sign it. A normal signature over an identity-linked token is not equivalent. The final token, signature, randomizer, and epoch are bearer credential material and must never be logged.
VPN rollout boundary
VPN verification currently runs in reject_invalid compatibility mode: malformed and cryptographically invalid vouchers are rejected before handshake acceptance, while a completely missing voucher is still accepted so older clients can drain out. This is not full mandatory-voucher enforcement and must not be documented as such.
mode = reject_invalid
valid | invalid | missing | malformed | total
valid_ratio | invalid_ratio | missing_ratio | malformed_ratio
last_observation | last_error
VoucherVerifier verifies signatures and records aggregate outcomes, but it does not atomically spend the VPN token or keep a one-time redemption table. Therefore this path must not claim node-enforced one-time redemption. Sharing, replay, quota, and expiry controls remain issuance/protocol-version concerns until a versioned redemption contract is implemented.
Blind Vault V2 admission
Blind Vault V2 is the stronger anonymous admission path. A client discovers node-signed public issuer epochs, blinds an RFC 9474 admission message, obtains a blind signature through an entitlement backend, finalizes it locally, and sends it to the unchanged /api/vault/v1/lease route. The public API works only when Blind Vault is enabled and valid issuers are pinned.
GET /api/vault/v1/issuers
POST /api/vault/v1/lease
POST /api/vault/v1/put
POST /api/vault/v1/pull
POST /api/vault/v1/delete
Content-Type: application/vnd.aeronyx.blind-vault-v1
client blinds RFC 9474 admission message
-> entitlement backend authorizes issuance
-> isolated blind issuer signs blinded bytes only
-> client finalizes signature
-> node verifies active public epoch
-> atomic spend marker + random self-authenticating lease
-> ciphertext storage operations use lease-scoped keys/capabilities
Issuer isolation and key rotation
The private signing operation lives in the separate aeronyx-blind-issuer process. Its request contains only scheme version, public issuer-key fingerprint, and bounded blinded RSA bytes. It has no account model, storage database, or redemption visibility, supports coarse aggregate health, and provides a custody interface suitable for software keys or future HSM/KMS adapters.
Public issuer epochs carry canonical public DER, stable SHA-256 key IDs, validity bounds, and maximum lease TTL. Runtime updates require a separately pinned authority, monotonic generations, active-epoch continuity, and atomic persistence so rollback or removing a still-valid epoch fails closed.
Atomic spend, idempotency, and replay
Blind Vault V2 verifies the finalized credential and creates the lease in one immediate SQLite transaction. The domain-separated spend ID and lease are committed together; a spent credential cannot create a second lease. An exact retry of the same already-created lease remains idempotent.
V1 and V2 share the one-time spend table, but they remain scheme-separated: V1 stores the raw ticket identity and is linkable; V2 derives an unlinkable spend ID from the finalized credential. This atomic replay protection belongs to Blind Vault admission and must not be attributed to the VPN VoucherVerifier.
Observability and Nodeboard
Operator surfaces may show aggregate validity, capacity, signer health, and coarse failure buckets. They must not expose raw credentials or dimensions that reconnect issuance to redemption. last_observation and last_error are operational buckets, not a license to add token, wallet, lease, request, or per-user data.
Allowed aggregate evidence:
- VPN
valid,invalid,missing, andmalformedtotals and ratios - issuer active-key, key-count, reload, capacity, rate, timeout, and circuit-breaker counters
- Blind Vault aggregate lease, live-object, ciphertext-byte, expiry, and cleanup health
- coarse mode, epoch availability, last observation, and privacy-boundary status
Never expose:
- raw voucher tokens, signatures, randomizers, blinded messages, or spend IDs
- wallet, account, payment, membership, or social identity
- per-user redemption history, lease IDs, object IDs, capabilities, or request IDs
- client public IP, destination, DNS, route, message, or browsing metadata
- issuer private keys, backend provider errors, ciphertext, plaintext, or wallet-level traffic
Threat model and limitations
Blind signatures reduce issuance-redemption linkability, not every side channel. Network timing, region choice, capacity, compromised clients, issuer over-collection, credential theft, collusion, and traffic correlation still require blind relay, encrypted payloads, bounded logs, route diversity, and careful deployment separation.
- issuer-side collection that can be correlated with redemption timing
- credential theft, sharing, resale, or client storage compromise
- VPN voucher replay until a versioned redemption policy exists
- malicious or colluding issuer, backend, node, and operator components
- timing, region, capacity, and traffic-correlation side channels
- key-rotation rollback, inactive epochs, or broken continuity
- telemetry that silently grows from aggregate health into per-user history
Source map
The source is split intentionally. VPN verification, isolated private signing, stable wire contracts, Blind Vault admission, public API, configuration, and health reporting have separate ownership boundaries that should remain visible in code review.
| Layer | Repository path | Role |
|---|---|---|
| VPN verifier | crates/aeronyx-server/src/voucher_verifier.rs | Parses AVCH, discovers epoch keys, verifies final vouchers, and records aggregate rollout metrics. |
| Blind signer | crates/aeronyx-blind-issuer/src/signer.rs | Owns identity-free RSA blind-signing policy and key-custody abstraction. |
| Issuer API | crates/aeronyx-blind-issuer/src/api.rs | Provides authenticated bounded signing, public epochs, pressure controls, and aggregate health. |
| Wire contracts | crates/aeronyx-core/src/protocol/blind_vault.rs | Defines RFC 9474 admission messages, key epochs, spend IDs, frames, and signatures. |
| Blind Vault service | crates/aeronyx-server/src/services/blind_vault.rs | Verifies V1/V2 admission and atomically commits spend plus lease. |
| Blind Vault API | crates/aeronyx-server/src/api/blind_vault.rs | Exposes issuers, lease, put, pull, and delete routes with coarse errors. |
| Blind Vault config | crates/aeronyx-server/src/config_blind_vault.rs | Pins issuers and update authority, bounds TTL, and validates monotonic rotation. |
| Health and reporting | crates/aeronyx-server/src/api/vpn_health.rs and management/reporter.rs | Publishes aggregate node status without voucher secrets or wallet traffic. |
Developer rules
A change is complete only when cryptographic semantics, rollout policy, one-time spend behavior, observability, tests, and all language versions agree. Prefer a narrower truthful claim over combining two credential systems into one marketing promise.
- Keep VPN and Blind Vault credential semantics separate in names, code, telemetry, and docs.
- Do not claim mandatory VPN vouchers while missing credentials are accepted.
- Do not claim VPN one-time redemption without an atomic spend contract.
- Keep the blind signer free of account, wallet, lease, node, and redemption context.
- Preserve epoch continuity, fail-closed authority verification, and transaction atomicity.
- Expose aggregate health only; never log credential or identity dimensions.
- Update source tests, Nodeboard contracts, and all translations with every semantic change.