From 53945d7dccfc8a9e83ce074cb750d252db81f397 Mon Sep 17 00:00:00 2001 From: Patrick Mulligan Date: Wed, 1 Apr 2026 13:25:06 -0400 Subject: [PATCH] fix(nip05): allow hyphens and periods in usernames per NIP-05 spec NIP-05 spec states local-part MUST only use characters a-z0-9-_. The previous regex /^[a-z][a-z0-9_]*$/ rejected hyphens and periods. Updated to /^[a-z][a-z0-9._-]*[a-z0-9]$/ and added support for the root identifier "_" (_@domain) as described in the spec. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/extensions/nip05/managers/nip05Manager.ts | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/extensions/nip05/managers/nip05Manager.ts b/src/extensions/nip05/managers/nip05Manager.ts index 3efe85b8..d83fef1c 100644 --- a/src/extensions/nip05/managers/nip05Manager.ts +++ b/src/extensions/nip05/managers/nip05Manager.ts @@ -55,10 +55,12 @@ function generateId(): string { } /** - * Validate username format - * - Lowercase alphanumeric and underscore only + * Validate username format per NIP-05 spec + * - Characters allowed: a-z, 0-9, hyphen (-), underscore (_), period (.) * - Must start with a letter + * - Must not end with a hyphen, underscore, or period * - Length within bounds + * - Special case: "_" alone is the root identifier (_@domain) */ function validateUsername(username: string, config: Required): UsernameValidation { if (!username) { @@ -67,6 +69,11 @@ function validateUsername(username: string, config: Required): User const normalized = username.toLowerCase().trim() + // Special case: root identifier "_" per NIP-05 + if (normalized === '_') { + return { valid: true } + } + if (normalized.length < config.min_username_length) { return { valid: false, error: `Username must be at least ${config.min_username_length} character(s)` } } @@ -75,9 +82,10 @@ function validateUsername(username: string, config: Required): User return { valid: false, error: `Username must be at most ${config.max_username_length} characters` } } - // Only lowercase letters, numbers, and underscores - if (!/^[a-z][a-z0-9_]*$/.test(normalized)) { - return { valid: false, error: 'Username must start with a letter and contain only lowercase letters, numbers, and underscores' } + // NIP-05 spec: local-part MUST only use characters a-z0-9-_. + // Must start with a letter, must not end with separator + if (!/^[a-z][a-z0-9._-]*[a-z0-9]$/.test(normalized) && !/^[a-z]$/.test(normalized)) { + return { valid: false, error: 'Username must start with a letter, end with a letter or number, and contain only a-z, 0-9, hyphens, underscores, and periods' } } // Check reserved usernames