Documentation
¶
Overview ¶
Package invite implements the Contract-B agent-network INVITE token: a self-contained, UNSIGNED transport an admin emits so a future `identity enroll` can bootstrap trust in a network's root anchor.
Why unsigned is correct ¶
An invite CARRIES the network's PUBLIC trust anchor (the ed25519 ROOT public key) itself, plus where to reach the network (the relay) and the network id derived from that key. A signature would have to be verified under some key — but the key in question IS the anchor we are trying to establish trust in, so a self-signature proves nothing. Authenticity instead comes OUT OF BAND: the admin reads the Fingerprint (the vmnet1:… network id) to the member over a TRUSTED channel, and `enroll` asks the member to confirm it before trusting the anchor. The token is a convenience transport, not a trust root.
The token + URL format ¶
The bootstrap token is the invitePrefix ("vmenroll1:") followed by the base64url-NOPADDING encoding of JSON{ network_id, relay, root_pubkey }. The enroll URL is relay + "/enroll#" + the token (the token rides the URL fragment, after the '#').
The wire body uses snake_case keys (the FieldNetworkID / FieldRelay / FieldRootPubKey SSOT constants); root_pubkey is base64-std (padded) of the 32-byte ed25519 root key. The token uses base64url-NOPADDING so it is URL/fragment-safe.
The integrity binding (load-bearing) ¶
network_id MUST equal registry.NetworkID(root_pubkey). Decode RE-DERIVES the id from the carried key and rejects any mismatch (ErrNetworkIDMismatch), so a relay cannot advertise a victim network's id while substituting its OWN anchor key. Every field is also individually gated: the root key is base64-std, 32-byte, and small-order-rejected via registry.NewPublicKey; the relay is non-empty; the JSON is strict (unknown fields and trailing data rejected).
Decode accepts either a bare token or the full enroll URL (it reads the token from the fragment after the first '#'). Encode validates with the SAME rules it would accept on Decode, so a token Encode produces always round-trips.
Reuse ¶
This package reimplements no crypto: it builds on internal/identity/registry for NetworkID (the keypair-bound id derivation) and NewPublicKey (the wrong-length / small-order validated key constructor).
Index ¶
Constants ¶
const ( FieldNetworkID = "network_id" FieldRelay = "relay" FieldRootPubKey = "root_pubkey" )
Wire field names (SSOT). These are the EXACT snake_case keys of the invite's transported JSON body. They are referenced from the encode/decode paths and asserted by tests, never inlined.
const ( // ErrBadPrefix is returned when the token (after URL-fragment extraction) does // not start with invitePrefix. ErrBadPrefix = "invite: token must start with " + invitePrefix // ErrBadBase64 is returned when the token body is not valid base64url-nopad. ErrBadBase64 = "invite: token body is not base64url (no padding)" // ErrBadJSON is returned when the decoded body is not a strict JSON object // (bad JSON, unknown field, or trailing data — fail closed). ErrBadJSON = "invite: token body is not a valid invite JSON object" // ErrEmptyRelay is returned when relay is empty. ErrEmptyRelay = "invite: relay must not be empty" // ErrBadRootPubKey is returned when root_pubkey is not base64-std of a valid // 32-byte ed25519 public key (bad base64 / wrong length / small-order). ErrBadRootPubKey = "invite: root_pubkey must be base64-std of an ed25519 public key" // ErrNetworkIDMismatch is returned when network_id != NetworkID(root_pubkey). // This is the integrity check that binds the advertised id to the actual // anchor key, so a tampered or substituted network_id is rejected. ErrNetworkIDMismatch = "invite: network_id does not match NetworkID(root_pubkey)" )
Reject messages (SSOT — referenced from Decode/Encode and asserted by tests/callers, never inlined). Each failure mode is a distinct typed error so callers can tell a malformed token from a tampered one.
Variables ¶
This section is empty.
Functions ¶
func Encode ¶
Encode validates inv (the SAME rules as Decode, so a token Encode produces always round-trips) and returns the opaque token plus the enroll URL that carries the token in its fragment. It FAILS CLOSED: any validation failure returns a non-nil error and empty strings.
func Fingerprint ¶
Fingerprint returns the human-comparable out-of-band fingerprint, which IS the network_id (the vmnet1:… string). This is the value the admin reads to the member over a TRUSTED channel, and the value `identity enroll` will ask the member to confirm before it trusts the anchor.
Types ¶
type Invite ¶
type Invite struct {
// NetworkID is "vmnet1:" + hex(SHA-256(root pubkey)[:16]) — registry.NetworkID
// of RootPubKey. Decode re-derives and re-checks this binding.
NetworkID string
// Relay is the relay base URL, e.g. "https://chat.acme.com".
Relay string
// RootPubKey is base64-std (padded) of the 32-byte ed25519 ROOT public key.
RootPubKey string
}
Invite is a self-contained, UNSIGNED transport of a network's PUBLIC trust anchor plus where to reach it. It is deliberately unsigned: it CARRIES the root pubkey (the trust anchor itself), so authenticity comes from the out-of-band fingerprint comparison (Fingerprint), not from a signature.
func Decode ¶
Decode accepts EITHER a bare token (vmenroll1:…) OR an enroll URL whose fragment is the token (https://…/enroll#vmenroll1:…). It strips the prefix, base64url-nopad decodes, strictly JSON-unmarshals (unknown fields + trailing data rejected), and VALIDATES — including the critical network_id ⇔ root_pubkey integrity check. It FAILS CLOSED with a typed error on any failure.