Files
zeekay a1bf2bfa1e feat: RPC/interfaces codegen + cap spec-conformance (SPEC §2.3/§3) + rebrand finish
zapgen — interface (RPC) support:
- Parse `interface` decls (brace + whitespace forms, one parser) with
  1-based auto-assigned method ordinals; methods take ≤1 struct request and
  (after `returns`) ≤1 struct response, matching the spec README grammar.
- desugar: `interface` opens a whitespace block; method lines pass through
  verbatim (no @N). Block headers now honored only at file scope, so a
  struct field NAMED `interface`/`struct` stays a field (H4 preserved).
- emit: per interface a 1-based ordinal table, a typed Client over a
  Channel seam, and an abstract Handler + Dispatch<Iface> ordinal router,
  all over the new runtime call envelope. Method names exported.
- New `rpc` package: the ZAP call envelope (BuildRequest/ParseRequest,
  BuildResponse/ParseResponse, Call, Response, status codes), byte-compatible
  with the other runtimes' transport envelopes (Version2 header + router
  msgType flags). Adds zap.NewBuilderV2.
- examples/echo: generated service + end-to-end client↔dispatch↔handler test
  covering request/response, void, no-request, bare, handler-error, and
  unknown-ordinal paths. Golden fixtures for the interface codegen.

cap — reconcile to SPEC v1.1:
- B3 signature scope: sign SPEC §3 canonical bytes (Capability[0..164) ||
  canonical(Caveats), Kind:u32||len:u32||Value), via Cap.CanonicalBytes —
  one definition shared by signer and verifier; excludes Sig and heap
  indirection so caps verify cross-language. CapID = Hash32(Canonical||Sig).
- B2 delegation gate: define cross-cutting Permission bits (PermAttenuate
  1<<32, PermAudit, PermRoot); enforce SPEC §2.3 step 3d in VerifyChain and
  at mint time in Attenuate (refuse a cap the verifier would reject).
- B4/B6: VerifyRevocation is scheme-aware (dispatches on the tag byte);
  verifySig fails closed on SchemeReserved/unknown instead of falling back
  to ed25519. Verifier.VerifyRevocation added for PQ revocations.
- spec_test.go covers all four.

Rebrand finish + docs:
- Zero capnp/Cap'n Proto and zero hanzo/avalabs/capnproto.org tokens in
  zap-native source (zap.go doc neutralized; cap doc-comment brand refs
  generalized). LLM.md refreshed: zapgen shipped (not "forthcoming"), rpc
  package, interface DSL, cap signature scope. Ignore the agents example
  binary.

go build ./... and go test ./... -count=1 pass; race-clean on changed pkgs.
2026-06-20 00:02:22 -07:00

158 lines
5.3 KiB
Go

// Copyright (C) 2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package cap
import (
"errors"
"time"
)
// Issuance describes the request to mint a new root capability.
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
}
// errMissingSigner is returned when Issue/Attenuate are called without a
// concrete Signer. We don't ship a default — every cap must be signed.
var errMissingSigner = errors.New("cap: signer required")
// 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 Issue(in Issuance, signer Signer) (Cap, error) {
if signer == nil {
return Cap{}, errMissingSigner
}
if in.IssuedAt == 0 {
in.IssuedAt = time.Now().Unix()
}
issuer := signer.Public()
raw, err := buildCapBytes(in, issuer, signer)
if err != nil {
return Cap{}, err
}
return Wrap(raw)
}
// 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 Attenuate(parent Cap, holder [32]byte, permissions uint64, caveats []Caveat,
expiresAt int64, signer Signer) (Cap, error) {
if signer == nil {
return Cap{}, errMissingSigner
}
if signer.Public() != parent.Holder() {
// The signer must be the parent's holder; only the holder can
// delegate authority downward. Anything else would let a
// stranger mint children, defeating the chain's whole purpose.
return Cap{}, ErrChainBroken
}
// SPEC §7: refuse to build a cap that would fail its own verifier.
// The delegation gate (SPEC §2.3 step 3d) requires the parent to
// carry PermAttenuate or be a CapKindDelegate cap; mint-time
// enforcement means a child that VerifyChain would reject is never
// produced in the first place.
if parent.Permissions()&PermAttenuate == 0 && CapKind(parent.Kind()) != KindDelegate {
return Cap{}, ErrNotDelegable
}
parentExpiry := parent.ExpiresAt()
switch {
case expiresAt == 0:
expiresAt = int64(parentExpiry)
case parentExpiry != 0 && uint64(expiresAt) > parentExpiry:
expiresAt = int64(parentExpiry)
}
parentID := parent.ID()
in := Issuance{
Kind: parent.Kind(),
Target: parent.Target(),
Holder: holder,
Permissions: permissions & parent.Permissions(),
Parent: parentID,
IssuedAt: time.Now().Unix(),
ExpiresAt: expiresAt,
Caveats: caveats,
}
issuer := signer.Public()
raw, err := buildCapBytes(in, issuer, signer)
if err != nil {
return Cap{}, err
}
return Wrap(raw)
}
// buildCapBytes serializes a capability into the canonical ZAP wire
// format and signs it. The signed payload is SPEC §3 canonical bytes
// (Capability[0..164) || canonical(Caveats)), computed via
// Cap.CanonicalBytes so signer and verifier share one definition. After
// signing, the SigSize-byte signature is patched into the Sig field
// in-place. Returns the final wire bytes.
func buildCapBytes(in Issuance, issuer [32]byte, signer Signer) ([]byte, error) {
// Build each Caveat as its own ZAP-framed sub-message; the canonical
// list element is the full ZAP buffer length-prefixed by ListBuilder
// .AddObjectBytes.
caveatBufs := make([][]byte, len(in.Caveats))
for i, cv := range in.Caveats {
caveatBufs[i] = NewCaveatView(CaveatViewInput{
Kind: uint32(cv.Kind),
Value: cv.Value,
})
}
// First pass: build with Sig = zero. The Sig field's bytes are NOT in
// the signing scope (CanonicalBytes covers [0..164) + caveats only),
// so the zero placeholder does not affect the signature.
raw := NewCapabilityView(CapabilityViewInput{
Kind: in.Kind,
Target: in.Target,
Holder: in.Holder,
Issuer: issuer,
Permissions: in.Permissions,
Parent: in.Parent,
IssuedAt: uint64(in.IssuedAt),
ExpiresAt: uint64(in.ExpiresAt),
Caveats: caveatBufs,
// Sig left as the zero [SigSize]byte.
})
// Compute the canonical signing bytes from the just-built buffer using
// the same code path the verifier uses — no asymmetry between build
// and verify (SPEC §7).
c, err := Wrap(raw)
if err != nil {
return nil, err
}
sig, err := signer.Sign(c.CanonicalBytes())
if err != nil {
return nil, err
}
// Patch the sig field in-place. The Sig field sits at
// rootOff + capabilityViewSigOff in the final buffer.
sigOff := sigAbsOff(raw)
copy(raw[sigOff:sigOff+SigSize], sig[:])
return raw, nil
}