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

112 lines
3.8 KiB
Go

// Copyright (C) 2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package cap
import (
"encoding/binary"
)
// 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.
type Revocation struct {
CapID [32]byte
RevokedAt int64
RevokerSig [SigSize]byte
}
// revocationPayload serializes the bytes that get signed.
func revocationPayload(capID [32]byte, revokedAt int64) []byte {
out := make([]byte, 40)
copy(out[:32], capID[:])
binary.LittleEndian.PutUint64(out[32:], uint64(revokedAt))
return out
}
// Revoke produces a Revocation record signed by signer. The signer must
// be the cap's original issuer — only the issuer (or a delegated
// revocation authority, out of scope here) can revoke a cap.
func Revoke(c Cap, now int64, signer Signer) (Revocation, error) {
if signer == nil {
return Revocation{}, errMissingSigner
}
if signer.Public() != c.Issuer() {
return Revocation{}, ErrChainBroken
}
id := c.ID()
sig, err := signer.Sign(revocationPayload(id, now))
if err != nil {
return Revocation{}, err
}
return Revocation{
CapID: id,
RevokedAt: now,
RevokerSig: sig,
}, nil
}
// 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.
func VerifyRevocation(r Revocation, issuerPub []byte) error {
return Verifier{}.VerifyRevocation(r, issuerPub)
}
// 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.
func (v Verifier) VerifyRevocation(r Revocation, issuerPub []byte) error {
if len(issuerPub) == 0 {
return ErrIssuerUnknown
}
return v.verifySig(issuerPub, revocationPayload(r.CapID, r.RevokedAt), r.RevokerSig)
}
// EncodeRevocation marshals a Revocation into canonical ZAP wire bytes
// via the generated RevocationView builder. Symmetric to DecodeRevocation.
func EncodeRevocation(r Revocation) []byte {
return NewRevocationView(RevocationViewInput{
CapID: r.CapID,
RevokedAt: uint64(r.RevokedAt),
RevokerSig: r.RevokerSig,
})
}
// DecodeRevocation parses a ZAP-framed Revocation buffer back into the
// idiomatic Go struct. Returns ErrBadMagic / ErrTooShort on framing
// errors. Symmetric to EncodeRevocation.
func DecodeRevocation(b []byte) (Revocation, error) {
v, err := WrapRevocationView(b)
if err != nil {
return Revocation{}, err
}
return Revocation{
CapID: v.CapID(),
RevokedAt: int64(v.RevokedAt()),
RevokerSig: v.RevokerSig(),
}, nil
}