Skip to content

Social-recovery guardian shares (0243 — Apple-ADP recovery-contacts analogue)#337

Merged
crs48 merged 1 commit into
mainfrom
claude/0243-social-recovery
Jun 29, 2026
Merged

Social-recovery guardian shares (0243 — Apple-ADP recovery-contacts analogue)#337
crs48 merged 1 commit into
mainfrom
claude/0243-social-recovery

Conversation

@crs48

@crs48 crs48 commented Jun 29, 2026

Copy link
Copy Markdown
Owner

Building the non-coercible "recover without keeping a phrase" path the reframed escrow note recommends over custodial escrow — xNet's equivalent of Apple's recovery contacts, built on the Shamir secret-sharing already shipped in seed-recovery.ts.

What this adds

Two IdentityManager methods (engine):

  • createGuardianShares({ totalShares, threshold }) — splits a recoverable identity's phrase into k-of-n guardian shares (prompts for the passkey to read the phrase). The user hands the shares to trusted guardians out of band.
  • recoverFromGuardianShares(shares) — on a new device, reconstructs the phrase from any threshold shares, reproduces the same DID, and enrolls a local passkey.

Recovery is entirely user-to-user — the cloud is never involved, so it stays zero-knowledge and non-coercible by construction (nothing for anyone to compel). Mirrored in the test-bypass manager. 3 new tests: 2-of-3 round-trips to the same DID on a wiped device, too-few-shares fails, and a non-recoverable (PRF) identity is rejected. @xnetjs/identity typechecks clean; 8 manager tests green.

Next

This is the engine; the guardian enroll/recover UI (a Settings "set up trusted guardians" flow + an onboarding "recover with guardian shares" path) follows. Changeset: @xnetjs/identity minor; no user-visible behavior yet → skip-changelog.

🤖 Generated with Claude Code

…ontacts analogue)

The non-coercible 'recover without keeping a phrase' path the Apple-reframed
escrow note recommends: wrap the already-shipped Shamir crypto in IdentityManager
methods. createGuardianShares(config) splits a recoverable identity's phrase into
k-of-n shares (passkey-gated read); recoverFromGuardianShares(shares) rebuilds the
phrase from >=threshold shares on a new device, reproduces the same DID, and
enrolls a local passkey. Entirely user-to-user — the cloud is never involved, so it
stays zero-knowledge. Mirrored in the test-bypass manager. 3 new tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: xNet Test <test@xnet.dev>
@crs48 crs48 added the skip-changelog Exclude this PR from the changelog label Jun 29, 2026
@crs48 crs48 temporarily deployed to pr-337 June 29, 2026 02:40 — with GitHub Actions Inactive
@github-actions

github-actions Bot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Preview removed for PR #337.

github-actions Bot added a commit that referenced this pull request Jun 29, 2026
@crs48 crs48 merged commit d1f03e3 into main Jun 29, 2026
15 of 16 checks passed
@crs48 crs48 deleted the claude/0243-social-recovery branch June 29, 2026 02:47
github-actions Bot added a commit that referenced this pull request Jun 29, 2026
crs48 added a commit that referenced this pull request Jun 29, 2026
…nd to end) (#339)

Wires the social-recovery engine
([#337](#337)) into the UI — xNet's
**Apple recovery-contacts** analogue, the non-coercible "recover without
keeping a phrase" path the [reframed escrow
note](docs/plans/0243-p3-escrow-design.md) recommends over custodial
escrow.

## What this adds

- **Settings → Account → "Trusted guardians":** for a recoverable
identity, split it into **3 guardian share codes (any 2 recover)**, each
copy-pasteable. Hand one to each trusted person.
- **Onboarding → "Recover with guardian shares":** a
[`GuardianRecoveryScreen`](packages/react/src/onboarding/screens/GuardianRecoveryScreen.tsx)
collects the share codes and reconstructs the **same DID** on a new
device, enrolling a local passkey. New machine state `guardian-recovery`
+ `ENTER_GUARDIAN_SHARES` / `SUBMIT_GUARDIAN_SHARES` events + a provider
side-effect calling `recoverFromGuardianShares`.
- `@xnetjs/identity` `serializeShare` / `parseShare` for the opaque
`xnet-share:…` codes guardians copy around.

**Recovery is entirely user-to-user — the cloud is never involved**, so
it stays zero-knowledge and non-coercible, exactly like Apple's ADP
recovery contacts.

Machine-transition tests (27 onboarding tests) + a share-code round-trip
test; `@xnetjs/identity`, `@xnetjs/react`, and `apps/web` typecheck
clean. Changeset: `@xnetjs/identity` + `@xnetjs/react` minor, with a
user-facing changelog entry.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
crs48 added a commit that referenced this pull request Jun 29, 2026
…#342)

Marks [exploration
0243](docs/explorations/0243_[x]_ACCOUNT_VALIDATION_AND_RECOVERY_BINDING_THE_PAYER_TO_THE_PASSKEY.md)
complete: renames `[_]` → `[x]`, records a **Resolution** summary of
everything shipped, and reframes the last item (P3.1) from "implement
custodial escrow" to a **documented decision to decline it**.

## Why P3.1 is "done" by being declined

A WorkOS-gated KMS escrow that recovers from a login alone would make
xNet **coercible** — a subpoena or a compromised WorkOS account would
reach user data, the opposite of this exploration's guarantee. Following
Apple's model (ADP holds no key; recovery via a user-held key + recovery
*contacts*), we shipped the **non-coercible alternative** instead —
configurable **trusted-guardian social recovery** (Shamir;
#337/#339/#341), entirely user-to-user. A privacy-preserving escrow
*engine* (#335) remains for a future Apple-grade ZK-PIN +
rate-limiting-HSM design if a concrete enterprise need ever arises; the
naive custodial variant is intentionally not built.

## Delivered across 17 PRs

Validation hardening · recovery phrase + synced passkey + guardian
social recovery + Settings management · account/device ledger (schemas,
ops, binding→account, content-key re-wrap) · privacy-preserving escrow
engine · Apple-reframed escrow design note. **13/13 implementation, 9/9
validation.**

Docs-only → `skip-changelog`.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

skip-changelog Exclude this PR from the changelog

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant