Infrastructure

Verifier and issuer legitimacy

Use this when a community, relay, marketplace, grant programme, or credential consumer needs to decide which verifiers, issuers, or witness services are acceptable for a credential class. This answers "who verifies the verifier?" without forcing the people reviewing the verifier to become public targets.

Use this when a community, relay, marketplace, grant programme, or credential consumer needs to decide which verifiers, issuers, or witness services are acceptable for a credential class. This answers "who verifies the verifier?" without forcing the people reviewing the verifier to become public targets.

Fit

  • Status: supported today.
  • NIP-85 kind: 30385 identifier assertion.
  • Subject: a method-scoped verifier identifier such as verifier:proof-of-person:in-person:<verifier-pubkey>.
  • Canonical helper: canonicalVerifierSubject.
  • Helpers: contributeIdentifierAssertion and aggregateIdentifierContributions.
  • Proof version: v2 recommended.
  • Useful metrics: rank as verifier confidence, plus reaction_cnt as a count-like corroboration signal.

Subject design

  • Use verifier:<credential-class>:<method>:<verifier-id> when the community is deciding whether to trust a verifier for one credential class and method.
  • Use separate subjects for different methods. A verifier trusted for proof-of-person:in-person is not automatically trusted for proof-of-residency:document-check.
  • Use service subjects for verifier services only when the method and class are already captured elsewhere in the deployment policy.
  • Do not use a generic domain or pubkey score when the decision depends on a specific verification ceremony, credential class, or issuer role.

What to publish

  • A kind 30385 identifier assertion whose d tag is the canonical verifier subject and whose k tag is the deployment namespace.
  • A rank profile explaining whether the score means verifier legitimacy, issuer confidence, witness quality, method reliability, or incident status.
  • Proof v2 tags, accepted reviewer-circle policy, threshold, freshness, and the credential class being considered.
  • Companion records for verifier onboarding, audit results, revocation source, issuer metadata, accepted methods, and incident response.

Implementation recipe

  1. Define the credential class and verification method before reviewers sign.
  2. Canonicalise the verifier with canonicalVerifierSubject.
  3. Have an accepted reviewer circle publish proof v2 identifier contributions.
  4. Verify the exact subject, k namespace, circle, threshold, freshness, and metric bounds.
  5. Accept attestations from that verifier only when the concrete attestation artefact also passes holder binding, presentation, expiry, and revocation checks.

Worked example

examples/use-cases/verifier-issuer-legitimacy.ts
import {
  NIP85_KINDS,
  aggregateIdentifierContributions,
  canonicalVerifierSubject,
  contributeIdentifierAssertion,
  createTrustCircle,
} from 'nostr-veil'
import {
  defaultMembers,
  externalProfileKind,
  memberIndex,
  proofVersion,
  verifyUseCaseAssertion,
  withCreatedAt,
} from './_shared.js'

const slug = 'verifier-issuer-legitimacy'
const verifierSubject = canonicalVerifierSubject(
  'proof-of-person',
  'in-person',
  'aa'.repeat(32),
)
const circle = createTrustCircle(defaultMembers.map(member => member.pub))

const contributions = defaultMembers.map((reviewer, index) =>
  contributeIdentifierAssertion(
    circle,
    verifierSubject,
    externalProfileKind,
    {
      rank: 82 + index * 3,
      reaction_cnt: index + 2,
    },
    reviewer.priv,
    memberIndex(circle, reviewer.pub),
    { proofVersion },
  ),
)

export const assertion = withCreatedAt(aggregateIdentifierContributions(
  circle,
  verifierSubject,
  externalProfileKind,
  contributions,
  { proofVersion },
))

export const result = verifyUseCaseAssertion(slug, assertion, {
  kind: NIP85_KINDS.IDENTIFIER,
  subject: verifierSubject,
  subjectTag: 'k',
  subjectTagValue: externalProfileKind,
  circleId: circle.circleId,
  minDistinctSigners: 3,
  freshAfter: assertion.created_at - 300,
})

What to verify

  • Strict NIP-85 syntax and a valid proof v2.
  • Kind 30385, with d equal to the canonical verifier subject and k equal to the deployment namespace.
  • The reviewer circle is accepted for that credential class and method.
  • proof.distinctSigners meets the verifier-legitimacy threshold.
  • The rank meaning is documented before it affects verifier admission, warning, rate-limiting, or revocation.
  • The concrete credential or attestation being consumed still passes its own issuer, holder, presentation, expiry, revocation, and evidence checks.

What this proves

  • Distinct members of an accepted reviewer circle scored the same verifier subject.
  • The aggregate score is bound to the verifier subject, kind, and subject hint.
  • Individual reviewer identities are hidden inside the ring.

What not to claim

  • Do not claim a trusted verifier makes every credential true.
  • Do not claim nostr-veil verifies the underlying personhood, residency, professional status, or credential claim.
  • Do not claim verifier legitimacy replaces revocation, audit, appeal, or incident response.
  • Do not let trust for one credential class or method spill into another class without a separate assertion or explicit policy.

Failure handling

  • Reject verifier scores from unknown circles, stale manifests, wrong methods, wrong credential classes, or unaccepted namespaces.
  • Treat missing holder-binding, presentation, expiry, or revocation checks on the concrete credential as a verifier failure.
  • Publish downgrade, incident, or replacement assertions when a verifier is compromised, captured, negligent, or no longer accepted for a method.
  • Route borderline scores to manual review rather than making verifier acceptance permanent.

Operational requirements

Risk to handleRequired control
Verifier reputation becomes a single unchecked trust root.Scope trust by credential class, method, issuer key, accepted circle, expiry, revocation, appeal path, and incident response.
A trusted verifier can still issue a bad credential.Verify each concrete credential artefact, holder binding, presentation challenge, expiry, revocation, and evidence trail.
Reviewer pressure can capture verifier admission.Use anonymous threshold review, conflict rules, rotation, audit logs, and independent circles for high-stakes methods.
First-person credentials and verifier-issued credentials start from different trust directions.Treat first-person claims, social witnessing, verifier ceremonies, and anonymous circle endorsement as separate layers that policy combines explicitly.

Open questions for deployments

  • Does rank mean verifier admission, ongoing reliability, incident severity, or method confidence?
  • Which credential classes need method-scoped verifier subjects?
  • What revocation source tells clients that a verifier is no longer accepted?
  • Which Attestr-style, NIP-VA-style, NIP-58, or custom attestation artefacts can this verifier issue or witness?

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.

30382 User assertion

Nostr pubkey subjects. Subject hint: p.

30383 Event assertion

Nostr event id subjects. Subject hint: e.

30384 Addressable event assertion

NIP-33 address subjects. Subject hint: a.

30385 NIP-73/external identifier assertion

packages, relays, domains, vendors, and other identifiers. Subject hint: k.

10040 Trusted service provider declaration

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.

Passed 2026-06-23T06:52:48.000Z
Events
1/1 fetched from relay
Proof
3/3 threshold from a 3-member ring
Run
th2og0-c6f633c86f
  • 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
aba777849094...35841a4d

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.

Tampered metric

Published scores must still match the signed contribution aggregate.

Wrong subject

The d tag and subject hint must stay bound to the signed v2 proof.

Wrong kind

The assertion kind must match the profile and the signed v2 context.

Proof downgrade

New deployment profiles require proof v2.

Duplicate signer

Repeated key images must not increase the signer count.

Insufficient threshold

Removing a signature must fail the profile threshold.

Stale assertion

created_at must remain inside the freshness window.

Unknown circle

The circle ID must be accepted by deployment policy.

Unsigned policy

verifyProductionDeployment() should require a signed deployment bundle from a trusted publisher.

Relay mutation

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.

Next examples