Skip to content

Seitan V2 (P6.1): check the Keybase server side, old-style invites, and a real accept before P6.3 #194

Open

Description

@dezren39

Phase 6, step P6.1 (Seitan V2) has an open item from the Keybase admin path. It is not blocked by it: the local rules (expiry, single use, revocation, freshness) are implemented and tested. This item must be settled before P6.3 and P6.4 (invite events and links) ship, and before the e2e invite and accept test.

Branch p6-seitan (not pushed), commit 3eafe3b3 (pkgs/id/src/seitan/).

Verified by reading keybase/client (master, 2026-10-12)

  • Accept message: SigPayload in go/teams/seitan_v2.go has the fields stage, uid, eldest_seqno, ctime, invite_id, version, in that order. Our accept::message matches.
  • Encoding: kbcrypto.CodecHandle() sets only WriteExt. keybase/go-codec writes positive integers in their smallest form and strings as fixstr below 32 bytes, then str8. Our golden test assumes the same.
  • Signature: kbcrypto.NaclSigningKeyPrivate.Sign is plain ed25519.Sign over the payload, with no domain prefix. Keybase verifies with ed25519.Verify. We verify strictly, which accepts a subset of what Keybase accepts. Honest signers are unaffected.
  • ctime unit: V2 is milliseconds (handler.go, "For V2 this is ms since the epoch"). V1 and invite links are seconds.
  • Admin V2 check: verifySeitanSingleV2 (go/teams/handler.go) verifies the signature over the message rebuilt from the server-supplied UnixCTime. It has no freshness check on ctime.

Not verified: what must be checked

  1. Server side (closed source). The team/seitan_v2 endpoint receives sig, now, and invite_id, and forwards UnixCTime to the admin's handler. The signature covers ctime, so the server cannot alter it without failing the check. Still unknown: what the server does with a withheld or replayed accept, and whether it keeps any state that the admin relies on. This cannot be checked from the client repo.
  2. Keybase's own V2 invites are old-style. Team.InviteSeitanV2 (go/teams/teams.go) creates an SCTeamInvite with only type, name, and ID. IsNewStyleInvite is invite.MaxUses != nil (go/teams/chain.go), so the admin path does not check expiry or a use count for these invites. CanConsumeInvite and ConsumeInviteByID (go/teams/transactions.go) check only the completed-invite marker within one transaction. Expiry and single use in our design are our rules, not Keybase's. Still to check: how a V2 invite stays single-use across transactions (the chain's stored state). Until that is settled, Keybase must not be cited as the reference for single use or expiry.
  3. No accept sample from a real client. No Keybase test vector covers an accept or a signed accept. Capture one accept (uid, eldest_seqno, ctime, invite_id, and the signature) from a real Keybase client and check that seitan::accept::verify accepts its bytes. This is the only check of the accept bytes beyond the source reading.
  4. V3 (invite links). The invite-link path (verifySeitanSingleInvitelink, seconds-based ctime, AKey) is out of scope for P6.1 and is not checked here.

What the code assumes until these are checked

  • The accept message is Keybase's field set and encoding, as read above.
  • Expiry and single use are local rules, enforced by the admin's node on its own invite state, not by Keybase.
  • The freshness window (N23) is a local rule, not part of the accept bytes.

Reference: the S9 spike doc/2026-10-11T03-44-33Z_design_groups_events_delegation/spikes/s9-seitan/RESULTS.md, and the phase record doc/2026-10-12T19-11-31Z_phase_6_invites_pull_cli/.

Acceptance criteria

  • A real Keybase client accept sample (uid, eldest_seqno, ctime, invite_id, signature) is captured and seitan::accept::verify is shown to accept its bytes (golden test added).
  • The single-use question for old-style V2 invites across transactions is settled from keybase/client chain state, or Keybase is explicitly no longer cited as the reference for single use/expiry.
  • The server-side unknowns (withheld/replayed accepts, server state) are documented as accepted unknowns with a stated rationale, since the closed-source endpoint cannot be checked from the client repo.
  • This item is closed out before P6.3/P6.4 (invite events and links) ship, per the body's constraint.

Activity

  1. dezren39 commented on Oct 24, 2026

    @dezren39
    MemberAuthor

    id train triage (2026-10-24): needs-decision — project owner: settle the Keybase server-side admin path before P6.3 (local rules are implemented and tested).

  2. added
    kind/infrastructureInfrastructure / host-portability work
    source/humanFiled directly by a human
    priority/normalOrdinary priority (the default)
    effort/mMedium: expected under 1 day of work
    app/idPackage/app: id (pkgs/ or apps/ in this repo)
    meta/enrichedIntake review done: acceptance criteria, links, and normalized labels added
    on Oct 27, 2026
  3. dezren39 commented on Oct 27, 2026

    @dezren39
    MemberAuthor

    Intake review

    Phase 6 (P6.1) verification item for Seitan V2. The client-side reading is thorough and the local rules are implemented/tested per the body, but three checks remain open by the body's own account: server-side behavior (closed source — document as accepted unknown), old-style V2 invite single-use across transactions, and a real-client accept sample. Still live, and it gates P6.3/P6.4.

    Risks: Citing Keybase as the reference for expiry/single-use before check 2 settles would bake a wrong assumption into P6.3 invite events.
    Suggested approach: Capture the real accept sample first (it is the only end-to-end check of the verify path), then settle the chain-state question from keybase/client, then record the server-side unknowns as accepted with rationale.
    Related: Phase 6 PR #197 (invites, pull, CLI) in this repo; implementation on branch p6-seitan, pkgs/id/src/seitan/.

    Enriched by the fleet monitor (intake pass): labels normalized and acceptance criteria appended to the body. This is triage only — no work has been started.

  4. added
    work/assignedQueue worker lifecycle state
    queue/claimedQueue worker lifecycle state
    and removed on Nov 1, 2026
  5. 9 remaining items

  6. dezren39 commented on Nov 1, 2026

    @dezren39
    MemberAuthor

    agent-note:v1
    {"kind":"pickup","task_id":4803,"worker":"repo-worker-code","lane":"openchamber-scheduled-lane","stage":"claimed","note":"OpenChamber scheduled lane pickup 2026-11-01: issue #194 remains open; PR #197 is merged; no active PR for this residual verification task and no hard-stop labels. Revalidating acceptance items before edits."}

  7. added
    user/escalatedQueue or user escalation state
    queue/blockedQueue or user escalation state
    status/blockedQueue or user escalation state
    and removed
    queue/claimedQueue worker lifecycle state
    work/assignedQueue worker lifecycle state
    on Nov 1, 2026
  8. dezren39 commented on Nov 1, 2026

    @dezren39
    MemberAuthor

    agent-note:v1
    {"kind":"blocked","task_id":4803,"worker":"repo-worker-code","lane":"openchamber-scheduled-lane","stage":"blocked","note":"Escalated to Drew: an authorized real Keybase V2 accept capture is required. The isolated official-client signup attempt did not provision an account (exit 2; whoami empty). Please provide the signed accept fixture and invite public key on #194; do not include the invite token or credentials. Documentation PR #3768 records the settled chain-state result and accepted server-side unknowns."}

    Findings — PR #3768

    Old-style V2 single-use is settled for the normal sequential client flow. At keybase/client commit 952e87e, InviteSeitanV2 creates an invite without MaxUses or expiry. Old-style completion is emitted by CanConsumeInvite / ConsumeInviteByID, persisted as COMPLETED chain state, and excluded from later active-invite lookups; the normal handler repolls before consuming (chain state, active lookups, handler). This establishes sequential use after the accepting transaction commits and refreshed state is observed—not atomic exclusion of concurrent/stale handlers or server-side replay behavior.

    Server-side unknowns are accepted. The team/seitan_v2 service is closed source; whether it withholds/replays accepts or relies on its own state is not established by the client. Local admission verifies the signed proof and applies local expiry, freshness, and single-use state, so it does not rely on undocumented endpoint state. Withholding may still delay or deny service.

    Real-client sample — Drew action required. This laptop had no installed Keybase client/account. I verified the official Linux client package signature, extracted the CLI under an isolated temporary home, and tried signup --no-email; it exited 2 and whoami remained empty. There is no authorized account/team here to perform an actual accept, and no synthetic sample was made.

    Please use a Keybase account with access to a test team and a second authorized account to create and accept a Seitan V2 invite. Then provide the real uid, eldest_seqno, ctime, invite_id, exact signed msgpack bytes, signature, and that invite's public verification key so we can add the seitan::accept::verify golden test. Do not post the invite token or account credentials. The task is blocked/escalated and this issue remains open pending that fixture.

  9. added
    mode/onlineFully doable remotely: design, config, code, cloud
    and removed on Nov 1, 2026
  10. added a commit that references this issue on Nov 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

app/idPackage/app: id (pkgs/ or apps/ in this repo)effort/mMedium: expected under 1 day of workkind/infrastructureInfrastructure / host-portability workmeta/enrichedIntake review done: acceptance criteria, links, and normalized labels addedmode/onlineFully doable remotely: design, config, code, cloudpriority/normalOrdinary priority (the default)queue/blockedQueue or user escalation statequeue/queuedHas a queued task in the Supabase agent queuesource/humanFiled directly by a humanstatus/blockedQueue or user escalation stateuser/escalatedQueue or user escalation state

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions