Files
go/cap/cap_test.go
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

615 lines
19 KiB
Go

// Copyright (C) 2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package cap
import (
"bytes"
"crypto/ed25519"
"errors"
"testing"
)
// issuerKeyFn builds a Verifier.IssuerKey lookup over a known map of
// signers keyed by their Public() hash.
func issuerKeyFn(signers ...*Ed25519Signer) func([32]byte) ([]byte, error) {
m := make(map[[32]byte]ed25519.PublicKey, len(signers))
for _, s := range signers {
m[s.Public()] = s.PublicKey()
}
return func(h [32]byte) ([]byte, error) {
pk, ok := m[h]
if !ok {
return nil, ErrIssuerUnknown
}
return pk, nil
}
}
func mustSigner(t *testing.T) *Ed25519Signer {
t.Helper()
s, err := NewEd25519Signer()
if err != nil {
t.Fatalf("NewEd25519Signer: %v", err)
}
return s
}
// ----------------------------------------------------------------------------
// Round-trip: Issue → Bytes → Wrap → field equality.
// ----------------------------------------------------------------------------
func TestIssueRoundTrip(t *testing.T) {
signer := mustSigner(t)
var target, holder [32]byte
for i := range target {
target[i] = byte(i)
holder[i] = byte(255 - i)
}
in := Issuance{
Kind: uint32(KindIAMSession),
Target: target,
Holder: holder,
Permissions: 0xDEADBEEFCAFEBABE,
IssuedAt: 1700000000,
ExpiresAt: 2000000000,
Caveats: []Caveat{
{Kind: CaveatMaxAmount, Value: u64bytes(1_000_000)},
{Kind: CaveatRateLimit, Value: u32pair(60, 10)},
{Kind: CaveatIPCIDR, Value: []byte("10.0.0.0/8")},
},
}
c, err := Issue(in, signer)
if err != nil {
t.Fatalf("Issue: %v", err)
}
if c.Kind() != uint32(KindIAMSession) {
t.Errorf("Kind = %#x, want %#x", c.Kind(), uint32(KindIAMSession))
}
if c.Target() != target {
t.Errorf("Target mismatch")
}
if c.Holder() != holder {
t.Errorf("Holder mismatch")
}
if c.Issuer() != signer.Public() {
t.Errorf("Issuer mismatch")
}
if c.Permissions() != 0xDEADBEEFCAFEBABE {
t.Errorf("Permissions = %#x", c.Permissions())
}
if c.IssuedAt() != 1700000000 {
t.Errorf("IssuedAt = %d", c.IssuedAt())
}
if c.ExpiresAt() != 2000000000 {
t.Errorf("ExpiresAt = %d", c.ExpiresAt())
}
if c.NumCaveats() != 3 {
t.Fatalf("NumCaveats = %d, want 3", c.NumCaveats())
}
// Caveat decode round-trip.
cv0 := c.CaveatAt(0)
if cv0.Kind != CaveatMaxAmount || !bytes.Equal(cv0.Value, u64bytes(1_000_000)) {
t.Errorf("Caveat 0 mismatch: %+v", cv0)
}
cv1 := c.CaveatAt(1)
if cv1.Kind != CaveatRateLimit || !bytes.Equal(cv1.Value, u32pair(60, 10)) {
t.Errorf("Caveat 1 mismatch: %+v", cv1)
}
cv2 := c.CaveatAt(2)
if cv2.Kind != CaveatIPCIDR || string(cv2.Value) != "10.0.0.0/8" {
t.Errorf("Caveat 2 mismatch: %+v", cv2)
}
// Caveats() should agree with CaveatAt.
all := c.Caveats()
if len(all) != 3 || all[2].Kind != CaveatIPCIDR {
t.Errorf("Caveats() mismatch: %+v", all)
}
// Bytes() round-trips through Wrap.
rewrapped, err := Wrap(c.Bytes())
if err != nil {
t.Fatalf("re-Wrap: %v", err)
}
if rewrapped.Kind() != c.Kind() {
t.Errorf("re-Wrap field drift")
}
}
// ----------------------------------------------------------------------------
// Verify accepts a freshly minted cap and rejects expired / revoked /
// wrong-issuer / tampered caps.
// ----------------------------------------------------------------------------
func TestVerifyAcceptsFresh(t *testing.T) {
signer := mustSigner(t)
c, err := Issue(Issuance{
Kind: uint32(KindKMSAccess),
Permissions: 0xFF,
ExpiresAt: 2000000000,
}, signer)
if err != nil {
t.Fatal(err)
}
v := Verifier{IssuerKey: issuerKeyFn(signer)}
if err := v.Verify(c, 1700000000); err != nil {
t.Errorf("Verify rejected fresh cap: %v", err)
}
}
func TestVerifyRejectsExpired(t *testing.T) {
signer := mustSigner(t)
c, err := Issue(Issuance{
Kind: uint32(KindKMSAccess),
Permissions: 0xFF,
ExpiresAt: 1700000000,
}, signer)
if err != nil {
t.Fatal(err)
}
v := Verifier{IssuerKey: issuerKeyFn(signer)}
if err := v.Verify(c, 1700000001); !errors.Is(err, ErrExpired) {
t.Errorf("expected ErrExpired, got %v", err)
}
}
func TestVerifyRejectsRevoked(t *testing.T) {
signer := mustSigner(t)
c, err := Issue(Issuance{Permissions: 1}, signer)
if err != nil {
t.Fatal(err)
}
v := Verifier{
IssuerKey: issuerKeyFn(signer),
IsRevoked: func(id [32]byte) bool { return id == c.ID() },
}
if err := v.Verify(c, 1); !errors.Is(err, ErrRevoked) {
t.Errorf("expected ErrRevoked, got %v", err)
}
}
func TestVerifyRejectsUnknownIssuer(t *testing.T) {
signer := mustSigner(t)
other := mustSigner(t)
c, err := Issue(Issuance{Permissions: 1}, signer)
if err != nil {
t.Fatal(err)
}
// Verifier only knows 'other', not the cap's issuer.
v := Verifier{IssuerKey: issuerKeyFn(other)}
if err := v.Verify(c, 1); !errors.Is(err, ErrIssuerUnknown) {
t.Errorf("expected ErrIssuerUnknown, got %v", err)
}
}
func TestVerifyRejectsTamperedBuffer(t *testing.T) {
signer := mustSigner(t)
c, err := Issue(Issuance{Permissions: 1}, signer)
if err != nil {
t.Fatal(err)
}
// Flip a permission bit. The Permissions field sits at
// rootOff + capabilityViewPermissionsOff in the ZAP buffer.
tampered := append([]byte(nil), c.Bytes()...)
tampered[fieldAbsOff(tampered, capabilityViewPermissionsOff)] ^= 0x01
tc, err := Wrap(tampered)
if err != nil {
t.Fatal(err)
}
v := Verifier{IssuerKey: issuerKeyFn(signer)}
if err := v.Verify(tc, 1); !errors.Is(err, ErrSigMismatch) {
t.Errorf("expected ErrSigMismatch, got %v", err)
}
}
// ----------------------------------------------------------------------------
// Attenuate: child intersects permissions and child's parent = parent.ID().
// ----------------------------------------------------------------------------
func TestAttenuateIntersectsPermissions(t *testing.T) {
root := mustSigner(t)
child := mustSigner(t)
var target [32]byte
target[0] = 0xAB
parent, err := Issue(Issuance{
Kind: uint32(KindATSOrder),
Target: target,
Holder: root.Public(),
Permissions: PermAttenuate | 0b11110000,
ExpiresAt: 2000000000,
}, root)
if err != nil {
t.Fatal(err)
}
leaf, err := Attenuate(parent, child.Public(), 0b10100110, []Caveat{
{Kind: CaveatMaxAmount, Value: u64bytes(100)},
}, 0, root)
if err != nil {
t.Fatalf("Attenuate: %v", err)
}
if leaf.Permissions() != (0b11110000 & 0b10100110) {
t.Errorf("Permissions = %#b, want %#b", leaf.Permissions(), 0b11110000&0b10100110)
}
if leaf.Parent() != parent.ID() {
t.Errorf("Parent pointer not set")
}
if leaf.Issuer() != root.Public() {
t.Errorf("Child issuer should equal parent holder")
}
if leaf.Target() != target {
t.Errorf("Target should be inherited")
}
if leaf.ExpiresAt() != parent.ExpiresAt() {
t.Errorf("Expiry should default to parent")
}
}
func TestAttenuateRequiresParentHolderKey(t *testing.T) {
root := mustSigner(t)
imposter := mustSigner(t)
holder := mustSigner(t)
parent, err := Issue(Issuance{
Permissions: 0xFF,
Holder: root.Public(),
ExpiresAt: 2000000000,
}, root)
if err != nil {
t.Fatal(err)
}
// Imposter is not the parent's holder — must refuse.
if _, err := Attenuate(parent, holder.Public(), 0xFF, nil, 0, imposter); !errors.Is(err, ErrChainBroken) {
t.Errorf("expected ErrChainBroken, got %v", err)
}
}
func TestAttenuateCapsExpiryDownward(t *testing.T) {
root := mustSigner(t)
holder := mustSigner(t)
parent, err := Issue(Issuance{
Permissions: PermAttenuate | 0xFF,
Holder: root.Public(),
ExpiresAt: 1000,
}, root)
if err != nil {
t.Fatal(err)
}
leaf, err := Attenuate(parent, holder.Public(), PermAttenuate|0xFF, nil, 9999, root)
if err != nil {
t.Fatal(err)
}
if leaf.ExpiresAt() != 1000 {
t.Errorf("child expiry not clamped to parent: %d", leaf.ExpiresAt())
}
}
// ----------------------------------------------------------------------------
// VerifyChain: walks parent links, dies on revoked parent.
// ----------------------------------------------------------------------------
func TestVerifyChainHappyPath(t *testing.T) {
root := mustSigner(t)
mid := mustSigner(t)
leaf := mustSigner(t)
var target [32]byte
target[31] = 0xEE
rootCap, err := Issue(Issuance{
Kind: uint32(KindMPCSign),
Target: target,
Holder: root.Public(),
Permissions: PermAttenuate | 0xFF,
ExpiresAt: 2000000000,
}, root)
if err != nil {
t.Fatal(err)
}
// mid keeps PermAttenuate so it may in turn issue leafCap.
midCap, err := Attenuate(rootCap, mid.Public(), PermAttenuate|0x0F, nil, 0, root)
if err != nil {
t.Fatal(err)
}
leafCap, err := Attenuate(midCap, leaf.Public(), 0x07, nil, 0, mid)
if err != nil {
t.Fatal(err)
}
v := Verifier{IssuerKey: issuerKeyFn(root, mid, leaf)}
chain := []Cap{midCap, rootCap}
if err := v.VerifyChain(leafCap, chain, 0x04, target, leaf.Public(), 1700000000); err != nil {
t.Errorf("VerifyChain rejected good chain: %v", err)
}
}
func TestVerifyChainRejectsRevokedParent(t *testing.T) {
root := mustSigner(t)
mid := mustSigner(t)
leaf := mustSigner(t)
var target [32]byte
target[0] = 0x01
rootCap, _ := Issue(Issuance{Holder: root.Public(), Target: target, Permissions: PermAttenuate | 0xFF, ExpiresAt: 2000000000}, root)
midCap, _ := Attenuate(rootCap, mid.Public(), PermAttenuate|0x0F, nil, 0, root)
leafCap, _ := Attenuate(midCap, leaf.Public(), 0x07, nil, 0, mid)
revoked := midCap.ID()
v := Verifier{
IssuerKey: issuerKeyFn(root, mid, leaf),
IsRevoked: func(id [32]byte) bool { return id == revoked },
}
chain := []Cap{midCap, rootCap}
err := v.VerifyChain(leafCap, chain, 0x04, target, leaf.Public(), 1700000000)
if !errors.Is(err, ErrRevoked) {
t.Errorf("expected ErrRevoked, got %v", err)
}
}
func TestVerifyChainRejectsBrokenLink(t *testing.T) {
root := mustSigner(t)
mid := mustSigner(t)
leaf := mustSigner(t)
other := mustSigner(t)
var target [32]byte
rootCap, _ := Issue(Issuance{Holder: root.Public(), Target: target, Permissions: PermAttenuate | 0xFF, ExpiresAt: 2000000000}, root)
midCap, _ := Attenuate(rootCap, mid.Public(), PermAttenuate|0x0F, nil, 0, root)
leafCap, _ := Attenuate(midCap, leaf.Public(), 0x07, nil, 0, mid)
// Build an unrelated cap; pass it as the "parent" to break the link.
bogus, _ := Issue(Issuance{Holder: other.Public(), Target: target, Permissions: PermAttenuate | 0xFF, ExpiresAt: 2000000000}, other)
v := Verifier{IssuerKey: issuerKeyFn(root, mid, leaf, other)}
chain := []Cap{bogus, rootCap}
if err := v.VerifyChain(leafCap, chain, 0x04, target, leaf.Public(), 1700000000); !errors.Is(err, ErrChainBroken) {
t.Errorf("expected ErrChainBroken, got %v", err)
}
}
func TestVerifyChainRejectsOpNotPermitted(t *testing.T) {
root := mustSigner(t)
holder := mustSigner(t)
var target [32]byte
c, _ := Issue(Issuance{Holder: holder.Public(), Target: target, Permissions: 0b0010, ExpiresAt: 2000000000}, root)
v := Verifier{IssuerKey: issuerKeyFn(root, holder)}
if err := v.VerifyChain(c, nil, 0b0100, target, holder.Public(), 1); !errors.Is(err, ErrOpNotPermitted) {
t.Errorf("expected ErrOpNotPermitted, got %v", err)
}
}
func TestVerifyChainEmptyChainRequiresRoot(t *testing.T) {
root := mustSigner(t)
holder := mustSigner(t)
var target [32]byte
c, _ := Issue(Issuance{Holder: holder.Public(), Target: target, Permissions: 0xFF, ExpiresAt: 2000000000}, root)
v := Verifier{IssuerKey: issuerKeyFn(root, holder)}
if err := v.VerifyChain(c, nil, 0x01, target, holder.Public(), 1); err != nil {
t.Errorf("root cap with empty chain should verify: %v", err)
}
// Pretend it has a parent; empty chain should now fail.
tampered := append([]byte(nil), c.Bytes()...)
tampered[fieldAbsOff(tampered, capabilityViewParentOff)] = 0x99
bad, err := Wrap(tampered)
if err != nil {
t.Fatal(err)
}
// Tampered buffer fails sig check first; this is OK — both are
// "reject". We're just confirming we never silently accept.
if err := v.VerifyChain(bad, nil, 0x01, target, holder.Public(), 1); err == nil {
t.Errorf("VerifyChain accepted tampered cap with empty chain")
}
}
// ----------------------------------------------------------------------------
// Revocation.
// ----------------------------------------------------------------------------
func TestRevokeAndVerify(t *testing.T) {
signer := mustSigner(t)
c, _ := Issue(Issuance{Permissions: 1, ExpiresAt: 2000000000}, signer)
r, err := Revoke(c, 1234567890, signer)
if err != nil {
t.Fatalf("Revoke: %v", err)
}
if r.CapID != c.ID() {
t.Errorf("Revocation.CapID mismatch")
}
if r.RevokedAt != 1234567890 {
t.Errorf("Revocation.RevokedAt = %d", r.RevokedAt)
}
if err := VerifyRevocation(r, signer.PublicKey()); err != nil {
t.Errorf("VerifyRevocation: %v", err)
}
}
func TestRevokeRequiresIssuerKey(t *testing.T) {
signer := mustSigner(t)
imposter := mustSigner(t)
c, _ := Issue(Issuance{Permissions: 1, ExpiresAt: 2000000000}, signer)
if _, err := Revoke(c, 1, imposter); !errors.Is(err, ErrChainBroken) {
t.Errorf("expected ErrChainBroken from non-issuer revoke, got %v", err)
}
}
func TestVerifyRevocationRejectsTampered(t *testing.T) {
signer := mustSigner(t)
c, _ := Issue(Issuance{Permissions: 1, ExpiresAt: 2000000000}, signer)
r, _ := Revoke(c, 100, signer)
r.RevokedAt = 200 // tamper
if err := VerifyRevocation(r, signer.PublicKey()); err == nil {
t.Errorf("tampered revocation accepted")
}
}
// ----------------------------------------------------------------------------
// Caveat encoding round-trip for each kind.
// ----------------------------------------------------------------------------
func TestCaveatEncodingAllKinds(t *testing.T) {
signer := mustSigner(t)
var chainID, assetID, audience, nonce [32]byte
for i := range chainID {
chainID[i] = byte(i)
assetID[i] = byte(0xA0 + i&0x0F)
audience[i] = byte(0xC0 + i&0x0F)
nonce[i] = byte(0xE0 + i&0x0F)
}
cases := []Caveat{
{Kind: CaveatExpiresAt, Value: u64bytes(2000000000)},
{Kind: CaveatMaxAmount, Value: u64bytes(42)},
{Kind: CaveatDestChain, Value: chainID[:]},
{Kind: CaveatRateLimit, Value: u32pair(120, 30)},
{Kind: CaveatIPCIDR, Value: []byte("192.168.0.0/16")},
{Kind: CaveatAssetID, Value: assetID[:]},
{Kind: CaveatOpAllow, Value: u64bytes(0xF0F0F0F0)},
{Kind: CaveatMaxDepth, Value: []byte{0x05}},
{Kind: CaveatAudience, Value: audience[:]},
{Kind: CaveatNonceHash, Value: nonce[:]},
}
c, err := Issue(Issuance{Permissions: 1, ExpiresAt: 2000000000, Caveats: cases}, signer)
if err != nil {
t.Fatal(err)
}
if c.NumCaveats() != len(cases) {
t.Fatalf("NumCaveats = %d, want %d", c.NumCaveats(), len(cases))
}
for i, want := range cases {
got := c.CaveatAt(i)
if got.Kind != want.Kind {
t.Errorf("[%d] Kind = %d, want %d", i, got.Kind, want.Kind)
}
if !bytes.Equal(got.Value, want.Value) {
t.Errorf("[%d] Value = %x, want %x", i, got.Value, want.Value)
}
}
}
// ----------------------------------------------------------------------------
// v1.1 wire shape: SigSize is 3408, footer carries algorithm tag.
// ----------------------------------------------------------------------------
// TestSigSize_V1_1 freezes the wire constant: the v1.1 footer is 3408
// bytes wide (sized for FIPS 204 ML-DSA-65 + 99-byte headroom, rounded
// to 16-byte alignment). If this constant ever changes, the wire bumps
// to v1.2 and every encoder/decoder consumer must update in lockstep.
func TestSigSize_V1_1(t *testing.T) {
if SigSize != 3408 {
t.Fatalf("SigSize = %d, want 3408 (v1.1 wire format)", SigSize)
}
if AlgTagOffset != SigSize-1 {
t.Fatalf("AlgTagOffset = %d, want SigSize-1 = %d", AlgTagOffset, SigSize-1)
}
if capabilityViewSize != 3572 {
t.Fatalf("capabilityViewSize = %d, want 3572 (164 + 3408)", capabilityViewSize)
}
if revocationViewSize != 3448 {
t.Fatalf("revocationViewSize = %d, want 3448 (40 + 3408)", revocationViewSize)
}
}
// TestEd25519Signer_WritesAlgTag asserts the Ed25519 stub writes the
// SchemeEd25519 (0x02) tag at AlgTagOffset of its produced signature.
// This is the wire contract the verifier dispatches on.
func TestEd25519Signer_WritesAlgTag(t *testing.T) {
signer := mustSigner(t)
sig, err := signer.Sign([]byte("test payload"))
if err != nil {
t.Fatalf("Sign: %v", err)
}
if sig[AlgTagOffset] != byte(SchemeEd25519) {
t.Errorf("sig[%d] = %#x, want SchemeEd25519 = %#x",
AlgTagOffset, sig[AlgTagOffset], byte(SchemeEd25519))
}
}
// TestIssueRoundTrip_AlgTagPersisted asserts that a minted cap's Sig
// field on the wire carries the algorithm tag byte the Signer set. This
// closes the loop: signer writes the tag, builder embeds it, view reads
// it back unchanged.
func TestIssueRoundTrip_AlgTagPersisted(t *testing.T) {
signer := mustSigner(t)
c, err := Issue(Issuance{
Kind: uint32(KindIAMSession),
Permissions: 0xFF,
ExpiresAt: 2000000000,
}, signer)
if err != nil {
t.Fatalf("Issue: %v", err)
}
sig := c.Signature()
if sig[AlgTagOffset] != byte(SchemeEd25519) {
t.Errorf("on-wire sig[%d] = %#x, want SchemeEd25519 = %#x",
AlgTagOffset, sig[AlgTagOffset], byte(SchemeEd25519))
}
}
// ----------------------------------------------------------------------------
// Framing checks.
// ----------------------------------------------------------------------------
func TestWrapRejectsShortBuffer(t *testing.T) {
if _, err := Wrap(make([]byte, 10)); !errors.Is(err, ErrTooShort) {
t.Errorf("expected ErrTooShort, got %v", err)
}
}
func TestWrapRejectsBadMagic(t *testing.T) {
// Buffer big enough to clear the framing length checks but with the
// magic field unset — should fail on the magic check.
b := make([]byte, 512)
if _, err := Wrap(b); !errors.Is(err, ErrBadMagic) {
t.Errorf("expected ErrBadMagic, got %v", err)
}
}
func TestWrapRejectsMismatchedCaveatLen(t *testing.T) {
signer := mustSigner(t)
c, _ := Issue(Issuance{Permissions: 1, ExpiresAt: 2000000000, Caveats: []Caveat{{Kind: CaveatMaxAmount, Value: u64bytes(1)}}}, signer)
// Truncate one byte off the end — the ZAP size header now claims
// more bytes than are present, so Parse must reject.
truncated := c.Bytes()[:len(c.Bytes())-1]
if _, err := Wrap(truncated); err == nil {
t.Errorf("expected error from truncated buffer, got nil")
}
}
// ----------------------------------------------------------------------------
// helpers
// ----------------------------------------------------------------------------
func u64bytes(v uint64) []byte {
out := make([]byte, 8)
for i := 0; i < 8; i++ {
out[i] = byte(v >> (8 * i))
}
return out
}
func u32pair(a, b uint32) []byte {
out := make([]byte, 8)
for i := 0; i < 4; i++ {
out[i] = byte(a >> (8 * i))
out[4+i] = byte(b >> (8 * i))
}
return out
}
// fieldAbsOff returns the absolute byte offset of a capability field
// inside the ZAP buffer raw. fieldOff is the offset within the root
// object (one of the capabilityView*Off constants). Used by tests that
// tamper with the wire bytes to verify signature rejection.
func fieldAbsOff(raw []byte, fieldOff int) int {
rootOff := int(raw[8]) | int(raw[9])<<8 | int(raw[10])<<16 | int(raw[11])<<24
return rootOff + fieldOff
}