Signed admission claim profile v1
Candidate profile · 5 September 2026 · not a complete Core wire standard
On this page
Scope and interpretation
This profile specifies one self-contained signed discovery-admission envelope. It does not specify network framing, a Beacon's canonical encoding, retention, authorization, or transport negotiation. Implementations claiming this candidate profile MUST follow the byte grammar below. Successful authentication means only that signatures and internal credential bindings check. It grants no permission.
Types and envelope
Offsets are zero-based and ranges exclude their end. All integers are unsigned big-endian; IDs, digests, and public keys are exactly 32 raw bytes. Signatures are 64 raw bytes. There are no optional fields, varints, padding, or trailing bytes.
Common header:
[0,1) version = 0x01
[1,2) proof = 0x01 sponsorship | 0x02 open cost
[2,34) logical domain
[34,66) Topic digest
[66,98) beneficiary public key
[98,130) descriptor digest
[130,138) epoch u64
Open cost (210 bytes total):
[138,146) nonce u64
[146,210) beneficiary signature
Sponsorship (442 bytes total):
[138,170) issuer public key
[170,202) credential beneficiary public key
[202,234) credential logical domain
[234,266) credential Topic digest
[266,274) credential epoch u64
[274,306) credential serial
[306,310) credential slots u32
[310,374) issuer signature
[374,378) selected slot u32
[378,442) beneficiary signature
An enclosing transport MUST enforce its own bounded framing before buffering an attacker's declared length. This envelope never requires more than 442 bytes. Unknown versions or proof tags MUST be rejected, not interpreted using defaults.
Hashes and signed bytes
Use SHA-256 and plain Ed25519, not Ed25519ph or Ed25519ctx. Prefix strings below are ASCII followed by exactly one NUL byte. Concatenation adds no separators.
credential_preimage = "SkeinNet/DiscoverySponsorship/v1\0"
|| envelope[138:310]
credential_id = SHA256(credential_preimage)
issuer_signature = Ed25519.Sign(issuer_key, credential_preimage)
claim_preimage = "SkeinNet/DiscoveryAdmission/v1\0"
|| envelope[2:138]
|| proof_payload
open proof_payload = 0x02 || nonce_u64
sponsor payload = 0x01 || credential_id || selected_slot_u32
claim_id = SHA256(claim_preimage)
beneficiary_signature = Ed25519.Sign(beneficiary_key, claim_preimage)
The action is fixed by the discovery-admission signing domain. Another action requires its own specified domain and valid permission. IDs exclude signature bytes; changing a signature must not create another allocation identity. The issuer signs its credential independently, while the beneficiary signs the credential digest and selected slot. Envelope version 1 identifies this grammar; it is not a negotiated network version.
Validation and errors
Decoding MUST check in this order: fewer than two bytes (length), unsupported version (version), unsupported tag (proof_tag), then exact size (length). Only then may fields be sliced. Every decoded envelope re-encodes identically. The serializer rejects non-64-byte signatures with signature_length.
Authentication checks signature lengths, then for sponsorship requires matching beneficiary/domain/Topic/epoch, a nonzero slot count, and selected slot less than that count (credential_binding). It verifies the issuer signature (credential_signature), then the beneficiary signature (claim_signature). Open-cost authentication checks only the beneficiary signature. Errors describe local validation results, not mandated public network responses.
Authorization remains separate: require a valid matching descriptor, current scope and epoch, accepted issuer or sufficient work, policy limits, replay and conflict accounting, and durable commit where required. A correctly signed nonce can fail work difficulty; epoch 2^64−1 can authenticate while being unauthorized. Authentication MUST NOT create a verified admission receipt or reserve retention.
Evidence and remaining gaps
conformance/claims-v1.json contains 14 independent Python-generated fixtures, including signatures from two distinct test identities and malformed envelopes. The Rust adapter's inspect_claim_wire reports authorization_checked:false. Tests cover exact round trips, every truncation of three valid fixtures, and 5,216 individual bit flips across the two normal envelopes. This is bounded mutation coverage, not general fuzzing or an independent full implementation.
Test private seeds in the generator are public and MUST NOT be used as identities. Before promotion, resolve acceptance rules for noncanonical or small-order Ed25519 inputs across libraries, specify canonical descriptor digests and time mapping, and add authorization transition traces. Key rotation, revocation, network errors, and upgrades require separate profiles. This document does not change the existing admission or network policy.
7 September diagnostic update: the same-author Python and Rust adapters agree on 6,108 additional cases, including several weak/noncanonical-key acceptances. A complete identity-beneficiary open-cost envelope also authenticates in both without checking authorization. See docs/CROSS_LANGUAGE_AUTH_V1.md. These are preserved counterexamples, not selected acceptance rules. Resolve the point-encoding/key-class gate explicitly before promotion; agreement between libraries does not close it.
