Documentation
¶
Overview ¶
Package badgeverify defines the canonical wire format for a Pilot "verified address" badge and an offline verifier for it.
A badge is a detached Ed25519-signed credential, bound to a single node_id, that asserts the address was verified through an external identity provider (GitHub / Google / WorkOS). It deliberately carries NO raw external identity (no GitHub login, no email) — only which provider vouched and when. An app certifies a badge is genuine by verifying the issuer signature against a pinned public key, entirely offline: no network round-trip and no trust in the registry that served the badge.
THE BINDING RULE (read this before using Verify):
A badge is public — anyone can fetch any node's badge. A badge is only meaningful when checked against the node_id that the secure/handshake layer has *cryptographically authenticated* for the connection. Always confirm Badge.NodeID equals the authenticated peer's node_id, or use VerifyForNode which does it for you. Verifying the signature alone lets an attacker replay another node's valid badge.
Index ¶
Constants ¶
const Version = "v1"
Version is the canonical-format version tag carried in every badge. A future incompatible format change bumps this and adds a parser branch.
Variables ¶
var ( // ErrNoKey is returned when no pinned key matches the badge's kid (or // the keyring is malformed). Fail-closed: with no trust anchor, // nothing verifies. ErrNoKey = errors.New("badgeverify: no pinned issuer key for badge kid") // ErrBadSignature is returned when the signature does not verify. ErrBadSignature = errors.New("badgeverify: badge signature verification failed") // ErrMalformed is returned when the badge string is not well-formed. ErrMalformed = errors.New("badgeverify: malformed badge") // ErrExpired is returned when the badge's exp is in the past. ErrExpired = errors.New("badgeverify: badge expired") // ErrNodeMismatch is returned by VerifyForNode when the badge is for // a different node than the authenticated peer. ErrNodeMismatch = errors.New("badgeverify: badge node_id does not match peer") )
Functions ¶
func Canonical ¶
Canonical returns the exact byte string that is signed and that travels on the wire as the "badge" field. The issuer signs these bytes; the verifier checks the signature over the bytes it received and only then trusts the parsed fields.
Layout: pilotbadge:v1:<node_id>:<provider>:<verified_at>:<exp>:<kid>:<subject>
func CanonicalEnrollment ¶
func CanonicalEnrollment(e Enrollment) (string, error)
CanonicalEnrollment builds the signed bytes for an enrollment. Layout: pilotenroll:v1:<node_id>:<provider>:<commitment>:<issued_at>:<kid>
func CanonicalRecovery ¶
CanonicalRecovery builds the signed bytes for a recovery authorization. Layout: pilotrecover:v1:<node_id>:<new_pubkey>:<commitment>:<exp>:<nonce>:<kid>
Exp must be non-zero: a recovery authorization that never expires would be a permanent, replayable takeover token.
Types ¶
type Badge ¶
type Badge struct {
NodeID uint32 // the address this badge is bound to
Provider string // identity authority that vouched: "github" | "google" | "workos"
VerifiedAt int64 // unix seconds, coarsened to day granularity at issue time
Exp int64 // unix seconds; 0 means no expiry
Kid string // issuer key-id that signed this badge (selects the verify key)
Subject string // OPTIONAL public label (Tier 1); empty for Tier 0 (provider-only)
}
Badge is the decoded, structured form of a verified-address credential.
func Parse ¶
Parse decodes a canonical badge string WITHOUT verifying its signature. Use Verify/VerifyForNode for anything trust-bearing.
func Verify ¶
Verify parses badgeStr, then checks that sigB64 is a valid Ed25519 signature over the EXACT received bytes, made with the pinned issuer key named by the badge's kid, and that the badge has not expired.
It does NOT check the binding to a node — see the package doc and VerifyForNode. Callers that already hold the authenticated peer node_id should prefer VerifyForNode.
func VerifyForNode ¶
VerifyForNode is Verify plus the binding rule: it additionally requires the badge to be bound to peerNodeID (the node_id the secure/handshake layer authenticated for this connection). This is the safe entry point for apps.
type Enrollment ¶
type Enrollment struct {
NodeID uint32
Provider string
Commitment string // base64/hex HMAC; never the raw identity
IssuedAt int64
Kid string
}
Enrollment binds an address to a salted identity commitment. The raw external identity is never present; Commitment is HMAC(verifier_salt, external_id), opaque to everyone but the verifier.
func ParseEnrollment ¶
func ParseEnrollment(s string) (Enrollment, error)
ParseEnrollment decodes an enrollment string WITHOUT verifying its signature. It rejects any non-enrollment prefix (domain separation).
func VerifyEnrollment ¶
func VerifyEnrollment(s, sigB64 string) (Enrollment, error)
VerifyEnrollment checks the signature of an enrollment against the pinned key named by its kid.
type Recovery ¶
type Recovery struct {
NodeID uint32
NewPubKey string // base64 Ed25519 public key the address rotates to
Commitment string
Exp int64 // unix seconds; MUST be non-zero and is enforced
Nonce string // single-use; the registry rejects replays
Kid string
}
Recovery authorizes rotating NodeID to NewPubKey because the enrolled identity (Commitment) was re-proven. NewPubKey is bound into the signed bytes, so an intercepted authorization is useless to anyone who does not hold NewPubKey's private key.
func ParseRecovery ¶
ParseRecovery decodes a recovery string WITHOUT verifying its signature. It rejects any non-recovery prefix (domain separation).
func VerifyRecovery ¶
VerifyRecovery checks a recovery authorization: signature against the pinned (cold recovery-authority) key named by its kid, AND that it has not expired. The caller (registry) MUST additionally enforce single-use of Nonce and that Commitment matches the address's enrolled commitment.