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 an AnyEvent loop)
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.

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

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 an AnyEvent event loop to be running. Croaks if any required argument is missing or if on_success/on_failure are not CODE refs.

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 an AnyEvent event loop to be running. Croaks if any required argument is missing or if on_success/on_failure are not CODE refs.

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