Refuse to overwrite an existing account on register or password change

registerAccount() never checked whether a credential capsule already
existed at the locator address it was about to publish to. That
address is a NIP-33 replaceable event fully determined by login name +
password, so registering with a login name + password combination that
was already registered -- by mistake (double-clicking "create account",
confusing it with "sign in"), or by coincidence with someone else's
account under a shared login name -- silently replaced the existing
account's identity binding. The old everyday identity became
unreachable through that login unless the original recovery phrase was
still on hand, with nothing warning the user beforehand.

changePassword() had the identical gap on the new locator: rotating to
a password that happened to already be registered under the same login
name would silently destroy that other account.

Both flows now query for an existing capsule at the target locator
before publishing, and refuse with AccountAlreadyExistsError if one is
found (or a retry-oriented error if no relay could be reached to check).
Two new end-to-end tests reproduce both scenarios and confirm the
pre-existing account survives untouched.

Bumps docs/spec.md to v0.6 (§15.6, §18.1).
This commit is contained in:
thePR0M3TH3AN committed 2026-07-21 16:54:32 -04:00
1 parent 8c01a121df
commit 01fc7c5ed3
6 files changed
+158 -15

No files matched your search

+6 -4
View File
@@ -199,16 +199,18 @@ routing.
This build covers the protocol's Phase 0 (cryptographic core + test vectors)
and the load-bearing parts of Phase 1 (account MVP), verified two ways:
- **Protocol-level tests** (`npm test`, 77 tests): Argon2id/ScalarExpand/JCS/
- **Protocol-level tests** (`npm test`, 79 tests): Argon2id/ScalarExpand/JCS/
padding/timestamp/NIP-44 unit tests with the spec's exact byte layouts
(§11), and full end-to-end scenarios — create → clean-device login, phrase
recovery → new credentials → clean-device login, password change with
mandatory tombstone + NIP-09 deletion, relay-loss → replica repair
(including keyless recovery-event rebroadcast), generation-rollback
detection failing closed by default rather than only warning (§16.2 step
6), and initial profile/relay-list publication never overwriting an
existing kind 0/10002/10050 event when the everyday identity is an
imported nsec (§15.8, §28.1) — run against an in-memory mock relay
6), initial profile/relay-list publication never overwriting an existing
kind 0/10002/10050 event when the everyday identity is an imported nsec
(§15.8, §28.1), and registration/password-rotation refusing to overwrite
another account already registered at the same locator address (§15.6,
§18.1) — run against an in-memory mock relay
(`packages/core/src/test-support/mockRelay.ts`), since this sandbox has no
route to public Nostr relays. NIP-44 has additionally been cross-tested
directly against the `nostr-tools` reference implementation (conversation