mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
docs: capture offline root signing design
This commit is contained in:
@@ -0,0 +1,140 @@
|
||||
# WIP: Offline-root signing
|
||||
|
||||
Status: implementation paused while we confirm what “generic delegated
|
||||
signing” should mean.
|
||||
|
||||
## What we are trying to protect
|
||||
|
||||
A Buzz account has a routine key that remains available to the desktop app and
|
||||
may also be accessible to agents. That key should continue to sign ordinary
|
||||
Buzz events. For a sensitive action such as approving a pull request, we want
|
||||
an additional signature from an offline root.
|
||||
|
||||
The original PR event and its updates are already signed and content-addressed.
|
||||
The offline root therefore does not need to sign another copy of the payload or
|
||||
participate in a separate request/policy workflow. It needs to sign the exact
|
||||
event ID it reviewed.
|
||||
|
||||
A Nostr event has one author and one signature, and its ID commits to that
|
||||
author, its tags, and its content. We cannot append a second signature to an
|
||||
existing event without changing it. The root must publish a new signed event
|
||||
that references the original event.
|
||||
|
||||
## Delegation and countersigning are different
|
||||
|
||||
Tyler's comment that we will probably want this generically for delegated
|
||||
signing changes where the primitive should live, but it does not bring back the
|
||||
generic approval system.
|
||||
|
||||
There are two related operations:
|
||||
|
||||
- **Delegation:** a root authorizes another key to sign some class of future
|
||||
events on its behalf.
|
||||
- **Countersigning:** a root reviews an event that already exists and signs a
|
||||
reference to that exact event.
|
||||
|
||||
Ordinary delegation alone does not solve the high-assurance problem. If the
|
||||
routine key is compromised, an attacker can use whatever authority was
|
||||
delegated to it. High-assurance approval requires the second operation: the
|
||||
offline root must countersign each exact sensitive event.
|
||||
|
||||
The two operations can share a generic representation of the relationship
|
||||
between an account and its signing authority, but their verification rules and
|
||||
security properties must remain distinct.
|
||||
|
||||
## Proposed minimal primitive
|
||||
|
||||
The reusable protocol needs only two signed objects in addition to the event
|
||||
being approved.
|
||||
|
||||
First, the routine account publishes a root link. The link contains a root
|
||||
signature over the routine pubkey, and the routine profile signs the link in
|
||||
turn. This proves that both keys agree on the relationship.
|
||||
|
||||
Second, the root publishes an endorsement with:
|
||||
|
||||
- the exact subject event ID;
|
||||
- the exact account/root-link event ID;
|
||||
- the routine account represented by the root; and
|
||||
- a short signed purpose such as `approve`.
|
||||
|
||||
The generic verifier checks the profile, subject, and endorsement signatures;
|
||||
the root-to-account link; the exact references; and the signed purpose. It does
|
||||
not know anything about pull requests.
|
||||
|
||||
The relay may use that same verifier before storing an endorsement. It should
|
||||
validate the signing relationship and referenced events, but it should not
|
||||
implement PR-specific rules.
|
||||
|
||||
## Pull requests become a thin consumer
|
||||
|
||||
For a pull request, the subject is the current signed revision:
|
||||
|
||||
- the latest trusted kind `1619` update, when one exists; or
|
||||
- otherwise the original kind `1618` pull-request event.
|
||||
|
||||
The PR code treats a valid endorsement with purpose `approve` as an approval by
|
||||
the routine account. If a new update is published, its event ID is different,
|
||||
so an endorsement of the previous revision no longer counts. No separate
|
||||
staleness or payload-digest mechanism is required.
|
||||
|
||||
The desktop should export the subject event and account link as an offline
|
||||
signing package. The root secret remains on the offline device. The desktop
|
||||
only imports the resulting public endorsement event.
|
||||
|
||||
## What Tyler's comment changes
|
||||
|
||||
The underlying SDK and wire format should use generic event-signing language,
|
||||
not PR-specific types. The offline CLI should likewise sign an arbitrary exact
|
||||
event package. PR code supplies the subject and interprets `approve`.
|
||||
|
||||
The current direction is therefore mostly right, but any checks for PR kinds or
|
||||
the literal `approve` action belong in the PR feature, not in the generic
|
||||
endorsement builder or publisher.
|
||||
|
||||
This does **not** justify adding:
|
||||
|
||||
- approval policies;
|
||||
- request and decision records;
|
||||
- thresholds or quorum calculation;
|
||||
- a generic approval inbox;
|
||||
- copied payloads and payload digests; or
|
||||
- a separate proof-object hierarchy.
|
||||
|
||||
Those features solve different problems and were responsible for most of the
|
||||
earlier size and complexity.
|
||||
|
||||
## Root rotation and revocation
|
||||
|
||||
Changing or removing the root must itself require authorization from the
|
||||
current root. Otherwise a compromised routine key could remove the protection
|
||||
before performing a sensitive action.
|
||||
|
||||
We still need to decide how rotation affects old endorsements. There are two
|
||||
reasonable meanings:
|
||||
|
||||
- The endorsement proves that the root was valid when it signed. It remains
|
||||
valid historical evidence after rotation.
|
||||
- An endorsement may authorize a pending action only while its referenced root
|
||||
link is current. Rotation immediately prevents old-root endorsements from
|
||||
authorizing new execution.
|
||||
|
||||
For PR admission, the second rule is safer. We can preserve the old event as
|
||||
historical evidence while requiring the current account/root link when deciding
|
||||
whether the PR may merge.
|
||||
|
||||
## Questions to settle before continuing
|
||||
|
||||
1. Did Tyler mean classic future-event delegation, exact-event countersigning,
|
||||
or a shared protocol that supports both?
|
||||
2. Should the account/root relationship remain a tag on the routine profile,
|
||||
or become a dedicated replaceable event reusable by other signing features?
|
||||
3. Is the first version intentionally one root per account, or must it support
|
||||
multiple scoped delegates now?
|
||||
4. Should active authorization always require the currently linked root, while
|
||||
preserving older endorsements only as audit history?
|
||||
|
||||
My recommendation is to ship one root per account and one generic exact-event
|
||||
endorsement. Use it first for PR approvals, keep PR interpretation outside the
|
||||
generic verifier, and defer policies, quorum, and broader delegation until a
|
||||
concrete second consumer requires them.
|
||||
Reference in New Issue
Block a user