NAME

Net::Nostr::Identifier - Mapping Nostr keys to DNS-based internet identifiers

SYNOPSIS

use AnyEvent;
use Net::Nostr::Identifier;

my $pubkey = 'b0635d6a9851d3aed0cd6c495b282167acf761729078d975fc341b22650b07b9';

# Parse and validate an identifier
my ($local, $domain) = Net::Nostr::Identifier->parse('bob@example.com');

# Build the well-known URL
my $url = Net::Nostr::Identifier->url('bob@example.com');
# https://example.com/.well-known/nostr.json?name=bob

# Display name (root identifier _@domain shows as domain)
Net::Nostr::Identifier->display_name('bob@example.com');  # "bob@example.com"
Net::Nostr::Identifier->display_name('_@bob.com');         # "bob.com"

# Verify a nostr.json response
my $response = { names => { bob => $pubkey } };
if (Net::Nostr::Identifier->verify_response($response, 'bob', $pubkey)) {
    print "Verified!\n";
}

# Async verification via HTTP (requires AnyEvent and AnyEvent::HTTP)
my $cv = AnyEvent->condvar;
my $ident = Net::Nostr::Identifier->new;
$ident->verify(
    identifier => 'bob@example.com',
    pubkey     => $pubkey,
    on_success => sub { my ($relays) = @_; print "Verified!\n"; $cv->send },
    on_failure => sub { my ($reason) = @_; warn "Failed: $reason"; $cv->send },
);
$cv->recv;

# Async lookup (find pubkey from identifier)
$cv = AnyEvent->condvar;
$ident->lookup(
    identifier => 'bob@example.com',
    on_success => sub { my ($pubkey, $relays) = @_; $cv->send },
    on_failure => sub { my ($reason) = @_; $cv->send },
);
$cv->recv;

DESCRIPTION

Implements NIP-05 for mapping Nostr public keys to DNS-based internet identifiers. An identifier has the form local-part@domain where the local-part uses only characters a-z0-9-_..

Clients verify an identifier by fetching https://<domain>/.well-known/nostr.json?name=<local-part> and checking that the returned "names" mapping contains the expected public key.

The special identifier _@domain is treated as a root identifier and may be displayed as just the domain.

OPTIONAL HTTP DEPENDENCY

Pure helpers such as parse, url, display_name, verify_response, and extract_relays do not load an HTTP client.

The network methods lookup and verify lazy-load AnyEvent::HTTP. The Net::Nostr::Core distribution recommends AnyEvent::HTTP but does not require it. Installing the Net::Nostr shim distribution pulls it in as a hard dependency for the full-stack install.

CLASS METHODS

parse

my ($local_part, $domain) = Net::Nostr::Identifier->parse('bob@example.com');

Splits an identifier into its local-part and domain. Strictly validates both parts. Croaks if the identifier is invalid:

  • missing or multiple @

  • empty local-part or domain

  • local-part contains characters outside a-z0-9-_.

  • domain contains whitespace, /, :, ?, #, [, ], or control characters

Bracketed IPv6 addresses (e.g. [::1]) are rejected because NIP-05 is DNS-based. Ports are rejected because the spec constructs HTTPS URLs from the domain directly.

url

my $url = Net::Nostr::Identifier->url('bob@example.com');

Returns the well-known URL for verifying the identifier.

display_name

my $name = Net::Nostr::Identifier->display_name('bob@example.com');  # "bob@example.com"
my $root = Net::Nostr::Identifier->display_name('_@bob.com');        # "bob.com"

Returns the display form of the identifier. Root identifiers (_@domain) are displayed as just the domain. All other identifiers are returned as-is.

verify_response

my $ok = Net::Nostr::Identifier->verify_response($hashref, $local_part, $pubkey);

Returns true if the response hashref maps the given local-part to the given pubkey. Keys must be 64-character lowercase hex (32-byte public keys).

extract_relays

my $relays = Net::Nostr::Identifier->extract_relays($hashref, $pubkey);

Returns an arrayref of relay URLs for the given pubkey, or an empty arrayref if none are found.

CONSTRUCTOR

new

Accepts named arguments as either a flat list or a single hash reference.

my $ident = Net::Nostr::Identifier->new(%args);

Croaks on unknown arguments.

base_url

Optional base URL override for testing. When set, HTTP requests go to this URL instead of https://<domain>.

INSTANCE METHODS

verify

$ident->verify(
    identifier => 'bob@example.com',
    pubkey     => $hex_pubkey,
    on_success => sub { my ($relays) = @_ },
    on_failure => sub { my ($reason) = @_ },
);

Asynchronously fetches the well-known URL and verifies that the identifier maps to the given pubkey. HTTP redirects are ignored per the spec. Requires AnyEvent and AnyEvent::HTTP. Croaks if any required argument is missing, if on_success/on_failure are not CODE refs, or if AnyEvent::HTTP is not installed.

The on_success callback receives an arrayref of relay URLs (may be empty). The on_failure callback receives an error reason string.

lookup

$ident->lookup(
    identifier => 'bob@example.com',
    on_success => sub { my ($pubkey, $relays) = @_ },
    on_failure => sub { my ($reason) = @_ },
);

Asynchronously fetches the well-known URL and returns the pubkey associated with the identifier. HTTP redirects are ignored per the spec. Requires AnyEvent and AnyEvent::HTTP. Croaks if any required argument is missing, if on_success/on_failure are not CODE refs, or if AnyEvent::HTTP is not installed.

The on_success callback receives the hex pubkey and an arrayref of relay URLs. The on_failure callback receives an error reason string.

SECURITY

The /.well-known/nostr.json endpoint MUST NOT return any HTTP redirects. This module ignores all HTTP redirects, treating them as verification failures.

SEE ALSO

NIP-05