Use this when the subject is an identity domain, NIP-05 name, upload provider, payment endpoint, or other service identifier outside a single Nostr pubkey.
Fit
- Status: supported today.
- NIP-85 kind: 30385 identifier assertion.
- Subject examples:
nip05:alice@example.com,domain:example.com,lnurlp:alice@example.com,nip96:https://upload.example.com. - Canonical helpers:
canonicalNip05Subject,canonicalDomainSubject,canonicalLnurlpSubject,canonicalNip96Subject, andcanonicalServiceSubject. - Helpers:
contributeIdentifierAssertion,aggregateIdentifierContributions. - Proof version: v2 recommended.
- Useful metrics:
rank.
Subject design
- Use kind 30385 because the subject is an identifier outside one Nostr pubkey.
- Decide whether the subject is a whole domain, a specific NIP-05 name, an LNURL/payment identifier, an upload endpoint, or a provider account.
- Canonicalise host names, schemes, trailing slashes, punycode, and case rules before anyone signs.
- Keep domain trust, NIP-05 name confidence, provider behaviour, and payment endpoint risk as separate subjects unless the profile deliberately combines them.
What to publish
- A kind 30385 assertion with the canonical identifier in
dand the profile namespace ink. - A
rankprofile explaining whether the score means identity confidence, domain-control confidence, provider reliability, abuse risk, or operational trust. - Proof v2 tags, accepted circle policy, threshold, freshness, and revocation or incident rules.
- Evidence references only when safe; DNS, HTTPS, NIP-05, and payment checks should happen before review, not inside the nostr-veil proof.
Implementation recipe
- Canonicalise the identifier before signing with the NIP-05, domain, LNURLp, NIP-96, or service helper: lowercase host names, agreed schemes, punycode handling, and trailing slash rules.
- Decide whether the subject is a domain, a specific NIP-05 name, a payment endpoint, or a service provider.
- Run the underlying service check outside nostr-veil, then publish the threshold-backed assessment.
- Require proof v2 and verify the expected identifier string,
knamespace, circle, threshold, and freshness. - Use expiry because domain ownership and service behaviour can change. In production, put resolver and service checks in
companionEvidenceso stale or missing off-chain evidence rejects automatically.
Worked example
import {
NIP85_KINDS,
aggregateIdentifierContributions,
contributeIdentifierAssertion,
createTrustCircle,
} from 'nostr-veil'
import {
defaultMembers,
externalProfileKind,
memberIndex,
proofVersion,
verifyUseCaseAssertion,
withCreatedAt,
} from './_shared.js'
const slug = 'nip05-domain-service-provider-trust'
const domainId = 'nip05:alice@example.com'
const circle = createTrustCircle(defaultMembers.map(member => member.pub))
const contributions = defaultMembers.map((reviewer, index) =>
contributeIdentifierAssertion(
circle,
domainId,
externalProfileKind,
{ rank: 80 + index * 2 },
reviewer.priv,
memberIndex(circle, reviewer.pub),
{ proofVersion },
),
)
export const assertion = withCreatedAt(aggregateIdentifierContributions(
circle,
domainId,
externalProfileKind,
contributions,
{ proofVersion },
))
export const result = verifyUseCaseAssertion(slug, assertion, {
kind: NIP85_KINDS.IDENTIFIER,
subject: domainId,
subjectTag: 'k',
subjectTagValue: externalProfileKind,
circleId: circle.circleId,
minDistinctSigners: 3,
freshAfter: assertion.created_at - 300,
})
Companion evidence
The proof carries the reviewer circle's assessment. The application still has to prove the underlying service facts it relies on. Model those checks as companion evidence in the signed deployment policy and derive them from collector or resolver output rather than static pass records. The default provider evidence ids are nip05-resolution, https-probe, and dns-owner-check. Use collectNip05DomainCompanionEvidence() when the verifier can fetch the NIP-05 document and HTTPS probe directly, and pass a deployment-specific DNS owner check. Use resolveNip05DomainCompanionEvidence() when those observations came from your own resolver or monitoring service.
const policy = createDeploymentPolicy(NIP05_DOMAIN_SERVICE_PROVIDER_TRUST_PROFILE, {
companionEvidence: nip05DomainCompanionEvidenceRequirements(subject, { maxAgeSeconds: 300 }),
expectedSubject: subject,
expectedSubjectTagValue: '0',
// accepted circles, metrics, freshness, and signature policy omitted here
})
const companionEvidence = await collectNip05DomainCompanionEvidence({
checkedAt: now,
checkDnsOwner,
expectedPubkey,
fetch,
subject,
})
const result = verifyProductionDeployment(assertionFromRelay, bundle, {
companionEvidence,
now,
trustedPublishers,
})
For nip05:<name>@<domain>, nip05-resolution should fetch and check the current NIP-05 document for that name. For broader domain:<host> or service subjects, customise the companion requirements to the exact DNS, HTTPS, LNURLp, NIP-96, or service probe your application depends on. If the application is not acting on a NIP-05 name, remove or mark nip05-resolution optional and require the service-specific probe that proves the fact you will rely on. The lower-level building blocks are fetchNip05DocumentEvidence() and probeHttpsService(). DNS ownership is deliberately a callback because deployments vary: some use DNSSEC, some use account-control checks, and some use an operator-controlled allow-list.
What to verify
- Strict syntax and a valid proof v2.
- Kind 30385, with
dequal to the exact canonical identifier andkequal to the identity/provider profile namespace. - The circle is trusted for identity or provider review, not merely any reputation circle.
- The assertion is fresh enough for domain or provider risk and has not been superseded by an incident signal.
- The application has independently resolved the underlying NIP-05, DNS, HTTPS, LNURL, or service check when that check matters. If the policy requires
companionEvidence, missing, failed, stale, or wrong-subject evidence must reject the deployment decision.
What this proves
- A circle rated the exact identifier string.
- The threshold and aggregate can be verified from the event.
- Proof v2 binds the contribution to the identifier assertion namespace.
What not to claim
- Do not claim the proof itself performs NIP-05, DNS, HTTPS, or LNURL resolution.
- Do not claim a domain-level score proves every name or endpoint on that domain is safe.
- Do not claim provider trust is permanent. Domains and hosted identities can change owner or behaviour quickly.
Failure handling
- Reject assertions for non-canonical names, wrong namespaces, unknown circles, stale reviews, or unresolved service checks.
- Split a broad domain assertion into specific NIP-05, endpoint, or provider assertions when clients need precision.
- Publish incident, revocation, or downgraded assertions when ownership, hosting, TLS, or service behaviour changes.
- Fall back to direct resolution and local policy when no trusted circle has reviewed the identifier.
Operational requirements
| Risk to handle | Required control |
|---|---|
| The proof does not prove domain control. | Perform DNS, HTTPS, NIP-05, LNURL, or service-specific checks before reviewers contribute. |
| The proof does not perform NIP-05 resolution. | Resolve and cache the NIP-05 result in the application; use nostr-veil only for the circle's assessment of that identifier. |
| Providers can change behaviour after review. | Use expiry, periodic re-review, incident assertions, and revocation once the profile supports it. |
| DNS, HTTPS, and provider accounts are not made anonymous. | Use normal operational security for lookups and account management; nostr-veil hides only which circle members contributed. |
Policy choices
- How are domains canonicalised: lowercase, punycode, trailing slash, scheme?
- Does
rankmean identity confidence, operational reliability, abuse risk, or provider trust? - Should a domain and a specific NIP-05 name be separate subjects?
- What expiry should identity trust carry?
Companion evidence smoke test
The companion-evidence smoke test runs the collector path for this profile and then verifies the derived evidence ids. The checked-in public report is a deterministic fixture dry run so the page stays reproducible; operators can run the same harness in live mode when they want npm, OSV, NIP-05, HTTPS, and relay I/O.
- Subject
nip05:jack@primal.net- Mode
- deterministic fixture dry run
- Evidence
- 3/3 passing
- Collector Returned All Evidence
- Resolver Fetched Current Document
- nip05-resolution: pass
- https-probe: pass
- dns-owner-check: pass
Run the deterministic check with npm run test:companion-evidence. For live I/O, run npm run test:companion-evidence:live.
NIP-85 kind reference
NIP-85 defines the assertion kind by the subject being scored. The kind number is part of the proof v2 context, so deployments should verify both the number and the subject hint tag.
Nostr pubkey subjects. Subject hint: p.
Nostr event id subjects. Subject hint: e.
NIP-33 address subjects. Subject hint: a.
packages, relays, domains, vendors, and other identifiers. Subject hint: k.
provider metadata, not a score assertion. Subject hint: provider tags.
Spec: NIP-85 trusted assertions.
Live relay test
The opt-in relay test signs this canonical example as real Nostr event data, publishes it to wss://relay.trotters.cc, fetches it back by id, and re-runs the application, syntax, Nostr signature, canonical tag, and proof checks.
- Events
- 1/1 fetched from relay
- Proof
- 3/3 threshold from a 3-member ring
- Run
tf7bic-3b3a5ae6d6
- Canonical example passes locally
- Relay stored and returned every signed event
- Fetched Nostr event signatures are valid
- Fetched tags match the canonical example
- NIP-85 syntax validation passes
- nostr-veil proof verification passes
- Deployment profile verifier passes
87b41f8d44e7...fedeb349
Run the same check with npm run test:use-cases:relay -- --write docs/use-case-relay-checks.json.
Safety checks
Each canonical use-case example is also exercised by an adversarial test harness. These are the failure modes a production verifier should reject before acting on the score.
Published scores must still match the signed contribution aggregate.
The d tag and subject hint must stay bound to the signed v2 proof.
The assertion kind must match the profile and the signed v2 context.
New deployment profiles require proof v2.
Repeated key images must not increase the signer count.
Removing a signature must fail the profile threshold.
created_at must remain inside the freshness window.
The circle ID must be accepted by deployment policy.
verifyProductionDeployment() should require a signed deployment bundle from a trusted publisher.
Fetched event content and tags must match the Nostr signature.
Use validateUseCaseProfileDefinition() for custom profiles, then verifyProductionDeployment() with trusted bundle publishers, signed relay events, accepted circle manifests, expected subject, freshness, and threshold policy so these checks are not left to application glue. For application UI and audit logs, use verifyProductionDeploymentReport() or createProductionDecisionReport() so failures include issue codes, remediation text, a recommended action, pass/fail/not-checked status for the controls, and the profile's proofClaims, proofLimitations, requiredControls, and recommendedActions.