Documentation
¶
Overview ¶
Package cap implements the ZAP capability runtime.
A Capability is a signed, attenuable token of authority. It grants a holder permission to perform a bitmask of operations on a target, with optional caveats. Caps form a chain: a parent's holder can issue an attenuated child cap whose permissions are a subset of the parent's. VerifyChain walks the chain back to a root, checking each signature, the intersection of permissions, expiry, revocation, and caveats.
Wire format is canonical ZAP — magic+version header, object data, length-prefixed list of Caveat sub-messages. The bytes are produced and read via the generated zero-copy views in capabilities_zap.go. This file is the thin idiomatic-Go wrapper that the IAM/KMS/MPC consumers see: Wrap / Cap.Field / Verifier.
Signature scope: per SPEC.md §3, the signed bytes are the canonical concatenation Capability[0..164) || canonical(Caveats) — the fixed header up to and including the Caveats list pointer, followed by each Caveat encoded as Kind:u32-LE || len(Value):u32-LE || Value in list order. This EXCLUDES the Sig field itself and the ZAP heap-area indirection bytes, and is recomputed identically by signer and verifier (see canonical.go, Cap.CanonicalBytes), so heap layout cannot be tampered with without breaking the signature and the signed bytes are identical across every language runtime.
Index ¶
- Constants
- Variables
- func EncodeRevocation(r Revocation) []byte
- func Hash32(b []byte) [32]byte
- func NewCapProofView(in CapProofViewInput) []byte
- func NewCapabilityView(in CapabilityViewInput) []byte
- func NewCaveatView(in CaveatViewInput) []byte
- func NewRevocationView(in RevocationViewInput) []byte
- func VerifyRevocation(r Revocation, issuerPub []byte) error
- type Cap
- func (c Cap) Bytes() []byte
- func (c Cap) CanonicalBytes() []byte
- func (c Cap) CaveatAt(i int) Caveat
- func (c Cap) Caveats() []Caveat
- func (c Cap) ExpiresAt() uint64
- func (c Cap) Holder() [32]byte
- func (c Cap) ID() [32]byte
- func (c Cap) IssuedAt() uint64
- func (c Cap) Issuer() [32]byte
- func (c Cap) Kind() uint32
- func (c Cap) NumCaveats() int
- func (c Cap) Parent() [32]byte
- func (c Cap) Permissions() uint64
- func (c Cap) Signature() [SigSize]byte
- func (c Cap) Target() [32]byte
- type CapKind
- type CapProofView
- type CapProofViewInput
- type CapabilityView
- func (t CapabilityView) Caveats() zap.List
- func (t CapabilityView) ExpiresAt() uint64
- func (t CapabilityView) Holder() [32]byte
- func (t CapabilityView) IssuedAt() uint64
- func (t CapabilityView) Issuer() [32]byte
- func (t CapabilityView) Kind() uint32
- func (t CapabilityView) Parent() [32]byte
- func (t CapabilityView) Permissions() uint64
- func (t CapabilityView) Sig() [3408]byte
- func (t CapabilityView) Target() [32]byte
- type CapabilityViewInput
- type Caveat
- type CaveatKind
- type CaveatView
- type CaveatViewInput
- type Ed25519Signer
- type Issuance
- type Revocation
- type RevocationView
- type RevocationViewInput
- type Scheme
- type Signer
- type Verifier
Constants ¶
const ( // PermAttenuate (bit 1<<32) — the holder may mint child caps whose // permissions are a subset of this cap's. SPEC §2.3 step 3d: a parent // MUST carry this bit (or be CapKindDelegate) for the verifier to // accept any attenuation off it. PermAttenuate uint64 = 1 << 32 // PermAudit (bit 1<<33) — the holder may read the audit trail for Target. PermAudit uint64 = 1 << 33 // PermRoot (bit 1<<63) — root-of-trust marker, set on root caps only. PermRoot uint64 = 1 << 63 )
Permission bits for Capability.Permissions (u64). Per capabilities_kinds.md "Permission bits": each CapKind owns the bottom 32 bits (their meaning is per-kind — verifiers MUST dispatch on CapKind first), and the top 32 bits are cross-cutting and identical across every CapKind.
Only the cross-cutting bits are normative wire-wide and therefore defined here; the per-kind low bits are owned by each consumer (IAM/KMS/ATS/Bridge) and declared in capabilities_kinds.md.
const AlgTagOffset = SigSize - 1
AlgTagOffset is the offset of the algorithm-tag byte within the SigSize signature footer. The byte at [SigSize-1] identifies which signature primitive a verifier MUST use; it is part of the signed payload, so a tag flip changes the signature and is caught by verifier mismatch.
const SigSize = 3408
SigSize is the fixed signature footer width in bytes. Sized at v1.1 to hold any of:
- secp256k1 ECDSA (65 bytes)
- Ed25519 (64 bytes)
- ML-DSA-65 (3309 bytes, FIPS 204 §5.2 Level-3)
- hybrid Ed25519+ML-DSA-65 (3373 bytes + 16-byte separator)
3408 is the smallest 16-byte-aligned size that fits FIPS 204 ML-DSA-65 (3309 B) with a 99-byte headroom. Schemes shorter than SigSize are zero-padded on the right; verifiers identify the scheme via the algorithm tag in the final byte (Sig[SigSize-1]) and decode the leading L_scheme bytes accordingly. See zap-spec/capabilities.zap and capabilities_kinds.md for the canonical tag table.
Variables ¶
var ( ErrTooShort = errors.New("cap: buffer too short") ErrBadMagic = errors.New("cap: bad magic") ErrBadCaveats = errors.New("cap: caveat block malformed") ErrSigMismatch = errors.New("cap: signature does not verify") ErrExpired = errors.New("cap: expired") ErrRevoked = errors.New("cap: revoked") ErrChainBroken = errors.New("cap: chain link broken") ErrPermsExceedPar = errors.New("cap: permissions exceed parent") ErrNotDelegable = errors.New("cap: parent does not permit attenuation") ErrOpNotPermitted = errors.New("cap: op not in permission mask") ErrTargetMismatch = errors.New("cap: target does not match") ErrHolderMismatch = errors.New("cap: holder does not match") ErrIssuerUnknown = errors.New("cap: issuer key unknown") ErrCaveatViolation = errors.New("cap: caveat violated") // ErrUnhandledScheme means the algorithm-tag byte is one the verifier // does not implement (or SchemeReserved / an unknown tag). It is both // the value a SchemeVerify hook returns to decline a tag (so the // dispatcher may try its built-in ed25519 bootstrap path for // SchemeEd25519) AND the terminal error the dispatcher returns when no // path handles the tag — fail-closed per SPEC §2.3 step 3c. ErrUnhandledScheme = errors.New("cap: signature scheme not handled") )
Errors returned by Wrap and chain validation.
Functions ¶
func EncodeRevocation ¶
func EncodeRevocation(r Revocation) []byte
EncodeRevocation marshals a Revocation into canonical ZAP wire bytes via the generated RevocationView builder. Symmetric to DecodeRevocation.
func Hash32 ¶
Hash32 is the package's canonical 32-byte hash function. Exposed so signers and verifiers agree on the digest construction. SHA-256 today, BLAKE3 in a later revision (see ID).
func NewCapProofView ¶
func NewCapProofView(in CapProofViewInput) []byte
NewCapProofView builds a ZAP-encoded CapProofView message from in and returns the bytes.
func NewCapabilityView ¶
func NewCapabilityView(in CapabilityViewInput) []byte
NewCapabilityView builds a ZAP-encoded CapabilityView message from in and returns the bytes.
func NewCaveatView ¶
func NewCaveatView(in CaveatViewInput) []byte
NewCaveatView builds a ZAP-encoded CaveatView message from in and returns the bytes.
func NewRevocationView ¶
func NewRevocationView(in RevocationViewInput) []byte
NewRevocationView builds a ZAP-encoded RevocationView message from in and returns the bytes.
func VerifyRevocation ¶
func VerifyRevocation(r Revocation, issuerPub []byte) error
VerifyRevocation checks that r is a valid revocation under issuerPub using the bootstrap scheme dispatch (ed25519 mandatory-to-implement, fail-closed on unknown/reserved tags). The caller is expected to have resolved the original cap's Issuer hash to a public key (via the same IssuerKey lookup the Verifier uses) and then call this with the resolved key.
For ML-DSA-65 / hybrid / secp256k1 revocations, use Verifier.VerifyRevocation with a SchemeVerify hook wired — the package function only carries the built-in ed25519 path.
Types ¶
type Cap ¶
type Cap struct {
// contains filtered or unexported fields
}
Cap is a zero-copy view over a capability buffer. Constructed by Wrap. All accessors read directly from raw without allocating; Value slices returned from CaveatAt / Caveats alias the underlying buffer.
func Attenuate ¶
func Attenuate(parent Cap, holder [32]byte, permissions uint64, caveats []Caveat, expiresAt int64, signer Signer) (Cap, error)
Attenuate derives a child cap from parent by intersecting permissions and adding caveats. The child's Issuer = parent's Holder; signer must hold the parent's holder key (this is the basis for chain validation: each link is signed by the previous holder's key).
The child target equals the parent's target — attenuation never broadens scope. permissions is intersected with the parent's permissions; passing 0xFFFF... is equivalent to "inherit all". caveats are appended (not replaced); chain validation evaluates them in conjunction with the parent's.
expiresAt of 0 inherits the parent's expiry; non-zero overrides downward (the child cannot outlive the parent).
func Issue ¶
Issue mints a new root capability signed by signer. The signer's Public() becomes the cap's Issuer field. Parent stays as supplied (zero for a true root; non-zero for re-issuing under an existing parent at the cost of caller asserting the chain).
To derive a child cap from an existing parent, use Attenuate.
func Wrap ¶
Wrap parses a capability buffer and returns a typed zero-copy view. Validates ZAP framing (magic, version, size) plus capability-specific structural checks (sig field within bounds). Cryptographic verification lives in Verifier.
func (Cap) CanonicalBytes ¶ added in v1.3.0
CanonicalBytes returns the exact bytes a Capability's signature is computed over, per SPEC.md §3 "Signature chain":
Capability[0..164) || for each Caveat in list order:
Kind:u32-LE || len(Value):u32-LE || Value
The fixed-header prefix is read verbatim from the wire buffer (so the signer and verifier agree on the on-wire field bytes), but the caveat section is RECOMPUTED from the decoded caveats rather than copied from the ZAP heap. This is deliberate: it excludes the heap-area pointer indirection bytes, so a tamperer cannot perturb the signature by rewriting heap layout, and it makes the signed bytes identical across language runtimes regardless of how each lays out its heap.
CanonicalBytes does NOT include Sig (bytes [164..3572)); that is the field being authenticated.
func (Cap) CaveatAt ¶
CaveatAt returns the i-th caveat. Out-of-range returns a zero Caveat. The Value slice aliases the underlying buffer; callers must not mutate it. Each call re-walks the variable-element list: O(i).
func (Cap) Caveats ¶
Caveats returns the slice of caveats decoded in one walk. Values alias the buffer; do not mutate.
func (Cap) ID ¶
ID returns the canonical 32-byte identifier of this cap. Per SPEC.md §4 the CapID is Hash32(CanonicalBytes(cap) || Sig) — the exact bytes signed at issue time plus the signature footer. Revocation records key on ID, and the chain walk matches each child's Parent to its parent's ID, so this construction is what binds the chain.
BLAKE3 swap-point: replace the SHA-256 in Hash32 with BLAKE3 once luxfi/crypto exposes a stable BLAKE3 entry point. Both are 256-bit; consumers should treat the ID as opaque bytes.
func (Cap) NumCaveats ¶
NumCaveats returns the number of caveats attached to this cap.
func (Cap) Permissions ¶
Permissions reads the permission bitmask.
type CapKind ¶
type CapKind uint32
CapKind enumerates the kinds of authority a capability can confer.
type CapProofView ¶
type CapProofView struct {
// contains filtered or unexported fields
}
CapProofView is a zero-copy view into a ZAP-encoded CapProofView message.
func WrapCapProofView ¶
func WrapCapProofView(b []byte) (CapProofView, error)
WrapCapProofView parses b and returns a typed view. Returns an error if the wire-level checks (magic, version, size) fail.
func (CapProofView) Chain ¶
func (t CapProofView) Chain() zap.List
func (CapProofView) Leaf ¶
func (t CapProofView) Leaf() CapabilityView
type CapProofViewInput ¶
CapProofViewInput collects the field values for NewCapProofView.
type CapabilityView ¶
type CapabilityView struct {
// contains filtered or unexported fields
}
CapabilityView is a zero-copy view into a ZAP-encoded CapabilityView message.
func WrapCapabilityView ¶
func WrapCapabilityView(b []byte) (CapabilityView, error)
WrapCapabilityView parses b and returns a typed view. Returns an error if the wire-level checks (magic, version, size) fail.
func (CapabilityView) Caveats ¶
func (t CapabilityView) Caveats() zap.List
func (CapabilityView) ExpiresAt ¶
func (t CapabilityView) ExpiresAt() uint64
func (CapabilityView) Holder ¶
func (t CapabilityView) Holder() [32]byte
func (CapabilityView) IssuedAt ¶
func (t CapabilityView) IssuedAt() uint64
func (CapabilityView) Issuer ¶
func (t CapabilityView) Issuer() [32]byte
func (CapabilityView) Kind ¶
func (t CapabilityView) Kind() uint32
func (CapabilityView) Parent ¶
func (t CapabilityView) Parent() [32]byte
func (CapabilityView) Permissions ¶
func (t CapabilityView) Permissions() uint64
func (CapabilityView) Sig ¶
func (t CapabilityView) Sig() [3408]byte
func (CapabilityView) Target ¶
func (t CapabilityView) Target() [32]byte
type CapabilityViewInput ¶
type CapabilityViewInput struct {
Kind uint32
Target [32]byte
Holder [32]byte
Issuer [32]byte
Permissions uint64
Parent [32]byte
IssuedAt uint64
ExpiresAt uint64
Caveats [][]byte
Sig [3408]byte
}
CapabilityViewInput collects the field values for NewCapabilityView.
type Caveat ¶
type Caveat struct {
Kind CaveatKind
Value []byte
}
Caveat is one constraint attached to a capability. Value bytes alias the underlying ZAP buffer when produced via Cap.CaveatAt / Cap.Caveats; callers must not mutate Value in-place. Caveat literals passed into Issue/Attenuate are copied during build.
type CaveatKind ¶
type CaveatKind uint32
CaveatKind enumerates the kinds of caveat that can be attached.
const ( CaveatExpiresAt CaveatKind = 0x00 CaveatMaxAmount CaveatKind = 0x01 CaveatDestChain CaveatKind = 0x02 CaveatRateLimit CaveatKind = 0x03 CaveatIPCIDR CaveatKind = 0x04 CaveatAssetID CaveatKind = 0x05 CaveatOpAllow CaveatKind = 0x06 CaveatMaxDepth CaveatKind = 0x07 CaveatAudience CaveatKind = 0x08 CaveatNonceHash CaveatKind = 0x09 )
type CaveatView ¶
type CaveatView struct {
// contains filtered or unexported fields
}
CaveatView is a zero-copy view into a ZAP-encoded CaveatView message.
func WrapCaveatView ¶
func WrapCaveatView(b []byte) (CaveatView, error)
WrapCaveatView parses b and returns a typed view. Returns an error if the wire-level checks (magic, version, size) fail.
func (CaveatView) Kind ¶
func (t CaveatView) Kind() uint32
func (CaveatView) Value ¶
func (t CaveatView) Value() []byte
type CaveatViewInput ¶
CaveatViewInput collects the field values for NewCaveatView.
type Ed25519Signer ¶
type Ed25519Signer struct {
// contains filtered or unexported fields
}
Ed25519Signer is a Signer backed by an ed25519 key. ed25519's native signature is 64 bytes; this stub places it at the leading bytes of the SigSize footer with the remaining bytes zero-padded and the algorithm tag (SchemeEd25519 = 0x02) at sig[AlgTagOffset]. The matching verifier (verifyEd25519) reads the leading 64 bytes back out and ignores the pad and tag byte.
This is intended for tests and bootstrap. Production PQ deployments plug an ML-DSA-65 Signer via a consumer's auth layer.
func NewEd25519Signer ¶
func NewEd25519Signer() (*Ed25519Signer, error)
NewEd25519Signer generates a fresh keypair.
func (*Ed25519Signer) Public ¶
func (s *Ed25519Signer) Public() [32]byte
Public returns the 32-byte hash of the ed25519 public key.
func (*Ed25519Signer) PublicKey ¶
func (s *Ed25519Signer) PublicKey() ed25519.PublicKey
PublicKey returns the raw 32-byte ed25519 public key. Used in tests to register the signer with a Verifier's IssuerKey lookup.
type Issuance ¶
type Issuance struct {
Kind uint32
Target [32]byte
Holder [32]byte
Permissions uint64
Parent [32]byte // zero = root
IssuedAt int64 // 0 = use time.Now()
ExpiresAt int64 // 0 = no expiry
Caveats []Caveat
}
Issuance describes the request to mint a new root capability.
type Revocation ¶
Revocation is the on-the-wire record stating that a particular cap is no longer valid. Revocations are gossiped/published out-of-band; the canonical store is a hash-set keyed on CapID.
The signature is over a 40-byte canonical payload:
[32]byte CapID || uint64 RevokedAt (little-endian)
The signature occupies the SigSize footer in the same shape as cap signatures (algorithm tag at byte SigSize-1, primitive's bytes at the leading L_scheme bytes, zero pad in between) so the verifier can reuse one signing primitive. The wire encoding uses ZAP framing via the generated RevocationView; this struct is the idiomatic Go container used at the boundaries.
func DecodeRevocation ¶
func DecodeRevocation(b []byte) (Revocation, error)
DecodeRevocation parses a ZAP-framed Revocation buffer back into the idiomatic Go struct. Returns ErrBadMagic / ErrTooShort on framing errors. Symmetric to EncodeRevocation.
type RevocationView ¶
type RevocationView struct {
// contains filtered or unexported fields
}
RevocationView is a zero-copy view into a ZAP-encoded RevocationView message.
func WrapRevocationView ¶
func WrapRevocationView(b []byte) (RevocationView, error)
WrapRevocationView parses b and returns a typed view. Returns an error if the wire-level checks (magic, version, size) fail.
func (RevocationView) CapID ¶
func (t RevocationView) CapID() [32]byte
func (RevocationView) RevokedAt ¶
func (t RevocationView) RevokedAt() uint64
func (RevocationView) RevokerSig ¶
func (t RevocationView) RevokerSig() [3408]byte
type RevocationViewInput ¶
RevocationViewInput collects the field values for NewRevocationView.
type Scheme ¶ added in v1.1.0
type Scheme uint8
Scheme is the wire-level signature algorithm tag. The numeric values MUST match capabilities_kinds.md "Wire schemes" table; the byte written at Sig[AlgTagOffset] is one of these constants.
Verifiers fail-closed on SchemeReserved and on values not enumerated here. A consumer's auth layer may re-export a typed alias whose constants reuse these numeric values one-for-one so the wire byte and the typed enum agree.
const ( // SchemeReserved (0x00) MUST NOT appear in valid caps. Verifiers // reject it so a zero-filled / never-initialised footer is caught. SchemeReserved Scheme = 0x00 // SchemeSecp256k1 is 65-byte secp256k1 ECDSA (R||S||v). SchemeSecp256k1 Scheme = 0x01 // SchemeEd25519 is 64-byte Ed25519 (RFC 8032). SchemeEd25519 Scheme = 0x02 // SchemeMLDSA65 is the 3309-byte FIPS 204 Level-3 ML-DSA-65 signature. SchemeMLDSA65 Scheme = 0x03 // SchemeHybrid is concatenated Ed25519 || ML-DSA-65 (64 + 3309 = 3373 B). SchemeHybrid Scheme = 0x04 )
type Signer ¶
type Signer interface {
// Sign returns a fixed-size signature over the supplied payload.
// The signature MUST verify under the Public() key on the verifier
// side. Implementations SHOULD be deterministic per call for
// replay-debugging and MUST NOT leak the secret key. The final
// byte (sig[AlgTagOffset]) MUST carry the algorithm tag.
Sign(payload []byte) ([SigSize]byte, error)
// Public returns the canonical 32-byte hash of the signer's public
// key. This must match the cap's Issuer field for Verify to accept
// the signature.
Public() [32]byte
}
Signer abstracts the issuer's signing key. The v1.1 wire format's signature footer (SigSize bytes, see cap.go) is wide enough for any supported primitive: secp256k1 ECDSA (65 B), Ed25519 (64 B), or ML-DSA-65 (3309 B per FIPS 204 §5.2). Implementations write their scheme tag at sig[AlgTagOffset] before signing so verifiers can dispatch on it.
This package only requires the interface; concrete signers live in a consumer's auth layer where the appropriate crypto dependency is wired in. The Ed25519Signer stub below is provided for tests and bootstrap.
type Verifier ¶
type Verifier struct {
IsRevoked func(capID [32]byte) bool
IssuerKey func(issuerHash [32]byte) ([]byte, error)
SchemeVerify func(scheme Scheme, pub []byte, payload []byte, sig [SigSize]byte) error
}
Verifier holds the policy dependencies cap validation needs:
IsRevoked is a side-channel lookup against the revocation list. Return true to reject the cap regardless of signature or expiry. A nil func is treated as "nothing revoked".
IssuerKey resolves an issuer's 32-byte hash back to its raw public key bytes (ed25519 raw pubkey or ML-DSA-65 FIPS 204 encoding). Must return ErrIssuerUnknown for unknown issuers.
SchemeVerify dispatches on the algorithm-tag byte at sig[AlgTagOffset] to validate the signature under the right primitive. A nil func means ed25519-only: a SchemeEd25519 tag uses the built-in bootstrap verifier and every other tag (including SchemeReserved / unknown) is refused fail-closed. Consumers that want ML-DSA-65 / hybrid / secp256k1 wire a hook; returning ErrUnhandledScheme from it for SchemeEd25519 falls back to the bootstrap path, so callers need not special-case the bootstrap.
func (Verifier) Verify ¶
Verify validates a single cap independent of chain context. Checks:
- signature is valid for the cap's Issuer (signed payload = CanonicalBytes per SPEC §3: Capability[0..164) || canonical(Caveats))
- not expired at the supplied now (unix seconds)
- not revoked
- caveat list parses cleanly
Returns nil if the cap is acceptable. Note that Verify does NOT walk the parent chain — use VerifyChain for that. A cap that passes Verify is structurally sound but may still be useless if its parent is revoked or its permissions don't include the op being attempted.
func (Verifier) VerifyChain ¶
func (v Verifier) VerifyChain(leaf Cap, chain []Cap, op uint64, target [32]byte, holder [32]byte, now int64) error
VerifyChain validates a cap proof end-to-end:
- leaf has not expired, is not revoked, has a valid signature
- leaf grants op (bit set in Permissions)
- leaf grants target (matches the supplied target)
- leaf grants holder (matches the supplied holder)
- chain links each parent ID to the next cap in chain
- every link verifies (signature, expiry, revocation)
- every link's permissions are a superset of the child's
- the root link (chain[len-1]) has Parent == zero
- caveats are walked at each level
Pass chain as parents nearest-to-leaf first: chain[0] is the leaf's parent, chain[len-1] is the root. An empty chain means leaf is a root (and the function only checks the leaf itself).
func (Verifier) VerifyRevocation ¶ added in v1.3.0
func (v Verifier) VerifyRevocation(r Revocation, issuerPub []byte) error
VerifyRevocation checks that r is a valid revocation under issuerPub, dispatching on the algorithm tag in r.RevokerSig[AlgTagOffset] exactly as cap signatures do (B4: scheme-aware, not hardcoded ed25519). The dispatch is fail-closed (SPEC §2.3 step 3c): a tag the verifier does not implement, or SchemeReserved (0x00), is rejected. Wire a SchemeVerify hook on the Verifier to accept ML-DSA-65 / hybrid / secp256k1 revocations.