Skip to content

feat(nip-oa): Owner Attestation — cryptographic proof of agent authorization#406

Merged
tlongwell-block merged 3 commits into
mainfrom
nip-oa-implementation
Apr 28, 2026
Merged

feat(nip-oa): Owner Attestation — cryptographic proof of agent authorization#406
tlongwell-block merged 3 commits into
mainfrom
nip-oa-implementation

Conversation

@tlongwell-block
Copy link
Copy Markdown
Collaborator

@tlongwell-block tlongwell-block commented Apr 27, 2026

Summary

Implements NIP-OA (Owner Attestation) across the full agent pipeline: Desktop → ACP → MCP → signed events.

When a human creates an agent in the desktop app, the owner key signs a reusable Schnorr credential (auth tag) proving the agent was authorized. This tag is transparently injected into every event the agent publishes — zero agent awareness required.

Desktop (owner + agent keys)
  └─ signs auth tag once at creation
  └─ persists in ManagedAgentRecord
  └─ passes via SPROUT_AUTH_TAG env var
       └─ ACP forwards to MCP server
            └─ MCP: sign_event() injects tag into every event

Tag Format

["auth", "<owner-pubkey-hex>", "<conditions>", "<schnorr-sig-hex>"]

Verifiers confirm the signature to prove the agent was authorized — without the owner key ever touching the agent runtime.

Security Properties

Property How
Fail-closed Malformed or invalid tags abort startup / agent creation
Strict boundary sign_event() enforces exactly 0 or 1 auth tags
Defense-in-depth send_event() validates pubkey binding + tag content
Atomic snapshot Owner keys locked once for token mint + auth tag
Profile sync kind:0 events also carry verified auth tag
Owner key isolation Secret key touched once at creation, never again

Files Changed (15)

Crate File What
sprout-sdk nip_oa.rs (new) compute, verify, parse primitives + 22 tests
sprout-sdk lib.rs module export
sprout-mcp main.rs resolve_auth_tag() startup helper + 6 tests
sprout-mcp relay_client.rs sign_event() injection, send_event() validation
sprout-mcp server.rs all 22 signing callsites → sign_event()
sprout-acp main.rs forward SPROUT_AUTH_TAG env var + 4 tests
desktop commands/agents.rs compute auth tag at creation (atomic)
desktop commands/agent_models.rs pass auth tag to profile sync
desktop managed_agents/types.rs auth_tag field + serde compat + 2 tests
desktop managed_agents/runtime.rs set env var at spawn
desktop relay.rs build_profile_event() + inject + 3 tests
desktop Cargo.toml / Cargo.lock sprout-sdk dependency
desktop check-file-sizes.mjs size threshold bump
e2e e2e_mcp.rs tool count assertion 44→49

Test Coverage

  • 22 tests — SDK crypto primitives (spec vectors, round-trip, malformed rejection, conditions validation)
  • 6 testsresolve_auth_tag() startup helper (valid, missing, empty, malformed, bad sig, non-Unicode)
  • 4 testssign_event() injection + boundary enforcement
  • 3 testssend_event() defense-in-depth (forged tag, unconfigured, wrong pubkey)
  • 4 tests — ACP env var forwarding
  • 3 tests — Desktop profile event auth tag injection
  • 2 tests — Desktop serde backward compatibility
  • E2E — 158/166 automated tests pass; auth tags verified in DB + REST API

Design Decisions

Decision Choice Why
Approach Pre-computed env var Fits existing pipeline, zero agent awareness
Conditions Empty for v1 Simplest; real revocation is API token rotation
Signing primitive sprout-sdk Pure math — keys in, tag out
Tag injection RelayClient::sign_event() Single chokepoint, compile-time enforcement
Malformed handling Warn + proceed without Never crash, never attach garbage

Backward Compatibility

Pre-existing agents have auth_tag: None via #[serde(default)]. They work without attestation. Re-attestation requires agent recreation (v2 scope).

Out of Scope (v1)

  • Time-bounded conditions / credential rotation
  • CLI auth tag signing tool
  • Client-side "authorized by" badge display
  • Relay-side verification / indexing
  • bot tag in kind:0 profiles (separate PR)

@tlongwell-block tlongwell-block force-pushed the nip-oa-implementation branch 3 times, most recently from f6c98e3 to fb19ecc Compare April 27, 2026 20:35
…ization

Implements NIP-OA (Owner Attestation) across the full agent pipeline:
Desktop → ACP → MCP → signed events.

Key features:
- SDK crypto primitives: compute, verify, parse auth tags
- Conditions validation per spec (kind=, created_at<, created_at>)
- Canonical decimal enforcement, no leading zeros
- MCP: sign_event() injection + send_event() defense-in-depth
- Desktop: atomic key snapshot, fail-closed, profile sync injection
- ACP: SPROUT_AUTH_TAG env var forwarding

94 SDK tests + 77 MCP tests. Codex CLI: 9.3/10 APPROVE.
* origin/main:
  Add multi-workspace support to desktop app (#409)
  feat(mobile): add #channel autocomplete to compose bar (#411)
  fix: close race window that dropped active channel messages (#410)
  feat(pulse): rich text editor with @mentions, media uploads, and formatting (#407)
  feat(mobile): multi-workspace support (#408)
  feat: always notify on DM messages like Slack/Discord (#405)
@tlongwell-block tlongwell-block merged commit d1c84a6 into main Apr 28, 2026
13 checks passed
@tlongwell-block tlongwell-block deleted the nip-oa-implementation branch April 28, 2026 13:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant