Decentralised Identity Verification for Nostr
Version: 0.1.0 (Draft) Date: 2026-03-02 Status: Draft specification — seeking community feedback Licence: MIT
Signet is an open protocol for decentralised identity verification on Nostr. It enables users to prove claims about their identity — age, parenthood, professional status — using zero-knowledge proofs, without revealing personal data or relying on a central authority.
The protocol defines four verification tiers, nine entity types, a continuous Signet Score, a verifier accountability framework, and uses a single generic Verifiable Attestation kind (31000, NIP-VA Verifiable Attestations) for all identity attestations, plus NIP-78 (kind 30078) for policies. A separate voting extension uses kinds 30482-30484. Every professionally verified person receives two credentials — a Natural Person (real identity with Merkle-bound attributes and document nullifier) and a Persona (anonymous alias with age-range proof only). This two-credential model makes privacy a first-class design goal.
Any Nostr client can implement Signet. Any community can set verification policies. Any licensed professional can become a verifier.
Child safety is the killer app. Social proof (blue checkmarks) drives adoption across all of Nostr.
- Motivation
- Design Principles
- Credential Tiers
- Signet IQ
- Service Policies
- Verifier Network
- Anti-Corruption Framework
- Event Kinds
- Cryptographic Stack
- Social Proof — The Blue Checkmark
- Alignment with Existing Standards
- Regulatory Compatibility
- Limitations
- Reference Implementation
- Identity Management & Peer Verification
- Identity Bridge
- Entity Type Classification
- Adversarial Resilience
- Civic Identity
- Two-Credential Verification
- Credential Lifecycle
- Child Online Safety
- Inclusivity
- Jurisdiction Confidence
- Implementation Levels
Nostr has no identity verification. Anyone can claim to be anyone. There is no blue checkmark, no web-of-trust scoring, no way to prove "I met this person" or "a professional verified this person's identity." The result: spam, impersonation, and zero basis for trust beyond "I recognise this pubkey."
Every kids' social platform that failed (Club Penguin, Messenger Kids, Wizz, Yubo) collapsed because safety was policy-based, not cryptographic. Adults could create child accounts to impersonate children. Age verification was self-declaration or nonexistent. No platform could prove:
- This parent is a real person
- This child is a real child
- This parent actually has this child
Nostr needs a trust layer that:
- Proves claims without revealing personal data
- Works without a central authority
- Starts with nothing (any account can get verified retroactively)
- Scales from casual peer vouching to professional identity verification
- Lets communities set their own policies
The world is moving to mandatory age verification:
- EU eIDAS 2.0 (December 2026): Every Member State must offer a digital identity wallet with selective disclosure
- France SREN law (April 2025): Adult sites must implement ZKP-based age verification
- UK Online Safety Act (2023): Platforms liable for children's exposure; Ofcom requires "highly effective age assurance"
- US COPPA 2.0 (pending): Extends protections to under-17
- US FTC COPPA flexibility (March 2026): FTC will not enforce against data collected solely for age verification — but requires robust deletion and clear notice. Signet exceeds this: no personal data is collected at all.
- Australia under-16 ban (2024): $50M AUD fines on platforms
- ISO/IEC 27566-1:2025: New global age assurance standard
Nobody has built this for the decentralised world. Signet fills that gap.
-
The credential states facts, the service sets policy. Credentials say "here's what was verified and how." Communities decide "here's what we require." Separation of attestation and enforcement.
-
Privacy by default. ZKP means proving a claim without revealing the underlying data. "This parent has a child aged 8-12" without revealing which parent, which child, or the exact age.
-
No central authority. Professional bodies (Law Society, Medical Board, notary commissions) are the trust anchors. No single entity approves verifiers. No DAO. No token.
-
Progressive — more than nothing is better than nothing. Users who refuse to show documents can still accumulate peer vouches. Less trust weight, but more than zero, which is what everyone has today.
-
Retroactive. Verification attaches to existing accounts. Any active Nostr identity — adult or child — can get verified at any time. Not tied to account creation.
-
Composable. Discrete tiers for policy gates + continuous score for reputation. Simple for end users, nuanced for power users.
-
Nostr-native. Built on secp256k1. Uses existing Nostr event infrastructure. Zero new dependencies for basic functionality.
Four tiers of verification, each declaring what was proven and by whom.
| Aspect | Detail |
|---|---|
| What's proven | Nothing — a signed claim: "I say I'm X" |
| Issuer | Self |
| ZKP hides | N/A |
| Use case | Baseline. What every Nostr account has today. |
| Trust weight | Minimal |
| Aspect | Detail |
|---|---|
| What's proven | N verified people (Tier 2+) attest: "I know this person/family" |
| Issuer | Peers |
| ZKP hides | Who vouched (proves count and minimum tier of vouchers without revealing identities) |
| Use case | Local communities, meetup groups, conferences. People who know each other. |
| Trust weight | Moderate. In-person vouches carry more weight than online vouches. |
| Threshold | Configurable per community. Default: 3 vouches from Tier 2+ accounts. |
| Aspect | Detail |
|---|---|
| What's proven | A licensed professional verified this adult's government-issued ID in person |
| Issuer | Professional (lawyer, doctor, notary) |
| ZKP hides | Which professional, the adult's real name, ID number, address — everything except "a professional verified this person" |
| Use case | High-trust communities. Adults who want strong verification without publishing their identity. |
| Trust weight | High |
| Aspect | Detail |
|---|---|
| What's proven | A licensed professional verified the adult's ID AND confirmed a child exists in a specific age range |
| Issuer | Professional |
| ZKP hides | Everything in Tier 3 + child's identity, exact age, which documents were shown |
| Evidence the professional sees | Parent's ID (passport, driving licence) + child evidence (birth certificate, passport, school record) |
| Use case | Maximum safety spaces. Proves the child is real and the parent is real. |
| Trust weight | Maximum |
A predator who is a verified real adult (Tier 3) could still create a fake child account. Tier 4 prevents this because:
- A professional independently confirms the child exists
- The professional sees evidence of the child (birth cert, passport)
- The professional's licence is on the line — fraudulent attestation = professional misconduct
- The ZKP cryptographically binds the parent's identity to the child's verified age range
- An adult pretending to have a child cannot produce a child's birth certificate to a lawyer
On top of discrete tiers, a continuous Signet IQ (0-200) measures identity quality — the statistical confidence that an identity is real, not fabricated. It is not a reputation score. A high IQ means independent, verifiable sources confirm this entity exists. What the entity does with that identity is a separate concern.
A score of 100 represents the confidence level equivalent to a human checking a passport face-to-face — the standard that governments deem acceptable. Scores above 100 indicate that the identity is harder to fabricate than a government-issued passport, because more independent sources confirm it.
| Signal | Weight | Notes |
|---|---|---|
| Professional verification (Tier 3/4) | Heavy | Single event, large impact |
Identity bridge (kind 31000, type: identity-bridge) |
Medium | Ring-sig proof linking anon account to verified identity, scaled by ring min tier |
| In-person peer signature | Strong | Met in person, signed keys face-to-face |
| Online vouch from verified user | Light | Accumulates — many light vouches add up |
| Account age | Passive | Time on the network adds weight gradually |
| Voucher's own Signet Score | Multiplier | A vouch from someone at IQ 150 carries more than from someone at IQ 40 |
| Signal | RECOMMENDED weight | Per-type cap (MUST) | Max contribution |
|---|---|---|---|
| Professional verification | 80 | 1 event | 80 |
| Identity bridge | 50 × (ringMinTier / 4) | 1 bridge | 50 |
| In-person vouch (Tier 2+) | 16 × (voucher_score / 200) | 3 vouches | 48 |
| Online vouch (Tier 2+) | 4 × (voucher_score / 200) | 5 vouches | 20 |
| Account age | 10 / year | 2 years (20 pts) | 20 |
Clients MAY adjust individual weight values but MUST enforce per-type caps. Without caps, vouch farming trivially inflates scores: 10 in-person vouches from high-score accounts would yield 160+ points, exceeding a professional verification.
The voucher-score tag is a snapshot of the voucher's Signet IQ at publication time. Vouchers MAY republish their vouch to update their embedded score. Clients MUST use the score from the most recent version of the vouch event.
The exact algorithm is implementation-defined (clients can weight signals differently), but the protocol specifies the signal types, their relative ordering, and per-type caps:
professional verification > identity bridge > in-person vouch > online vouch > account age
Clients MUST respect this ordering. A single professional verification always outweighs any number of online vouches. Clients MUST enforce per-type caps as specified in the weights table above. These two constraints together prevent gaming through vouch farms.
┌───────────────────────────────────┐
│ Alice ✓✓ Tier 3 │
│ Signet IQ: 106 │
│ │
│ ● Prof verified (lawyer) │
│ ● 4 in-person vouches │
│ ● 12 online vouches │
│ ● Account age: 2 years │
└───────────────────────────────────┘
- Tier = the gate (can you enter this space?)
- Signet IQ = the identity quality (how confident are we that this identity is real?)
- End users see a simple tier badge. Power users can drill into the score breakdown.
Each community, circle, relay, or client sets a minimum verification requirement.
| Community Type | Adult Minimum | Child Minimum | Example |
|---|---|---|---|
| Open | Tier 1 | Tier 1 | Public Nostr feed |
| Casual group | Tier 2 | Tier 2 | Local meetup group |
| Curated circle | Tier 3 | Tier 3 | Trusted community |
| Max safety | Tier 3 | Tier 4 | Children's learning space |
Services can also set:
- Minimum Signet Score: "Tier 2 AND iq > 100"
- Per-role requirements: "moderators need Tier 3, members need Tier 2"
- Child-specific overrides: "adults can be Tier 2, child accounts must be Tier 4"
Policy is enforced at the client level and optionally at the relay level:
- Client-side: Clients check Signet credentials before displaying content or allowing interaction.
- Relay-side (optional): AUTH-gated relays can require minimum verification tier before accepting events. This prevents unverified accounts from even publishing to protected spaces.
Anyone can become a verifier if they meet the criteria. No single entity approves verifiers. No DAO. No token.
Becoming a professional verifier:
- Publish a verifier attestation (kind 31000 with
type: verifier) containing your professional licence information (bar number, medical licence number, notary commission ID) - Get cross-verified by other professional verifiers (prevents fraudulent licence claims)
- You can now issue Tier 3 and Tier 4 attestations
- Your professional body is your accountability — fraudulent attestations = professional misconduct, loss of licence, potential criminal liability
Professional bodies as trust anchors:
The system doesn't create a new trust hierarchy. It uses the ones that already exist: the Law Society, medical licensing boards, notary commissions. These bodies already:
- Verify practitioners' identities
- Hold them to ethical standards
- Have disciplinary procedures
- Can revoke licences
Signet gives these existing trust relationships a cryptographic expression.
Cross-verification prevents gaming:
A fake lawyer can't just publish a credential claiming to be a lawyer. Other verified lawyers in the network must vouch for the credential. This creates a self-policing professional network — the same professionals who risk their licences have every incentive to call out fakes.
Becoming a peer verifier:
- Reach Tier 2+ yourself
- You can vouch for others you know
- In-person vouches (key signing at meetups, conferences) carry more weight than online vouches
- Your vouches' weight scales with your own Signet Score
The already-doxed advantage:
People who are already public figures (conference speakers, known community members) can vouch freely for people they meet in person. Their public identity IS their credential. When they sign someone's key at a meetup, that signature carries real weight because the voucher is already publicly accountable.
Professional Verification (Tier 3/4):
User ──── meets in person ────► Lawyer/Doctor/Notary
│
sees passport
(+ child evidence for Tier 4)
│
issues Signet credential
│
publishes to Nostr relay
│
User's account ◄──── credential attached ────┘
Peer Verification (Tier 2 / score):
User A ──── meets User B in person ────► key signing
│
mutual vouch events
published to relay
│
Both users' scores increase ◄───────────────┘
Online Vouching (score only):
Verified User ──── "I vouch for this person" ────► vouch event
│
Target user's score increases ◄───────────────────────┘
(weight depends on voucher's own score)
A registered professional can still be corrupt — doctors sell black market prescriptions, lawyers take bribes. No single measure prevents this. The defence is layered: make corruption expensive, detectable, and permanently traceable.
Every verifier attestation (kind 31000 with type: verifier) includes a licence number and jurisdiction. Any client can link directly to the relevant public register for users to verify:
| Jurisdiction | Register | URL |
|---|---|---|
| UK solicitors | SRA | sra.org.uk/consumers/register |
| UK doctors | GMC | gmc-uk.org/registration-and-licensing |
| US lawyers | State bar (per state) | Varies by state |
| Notaries | State/county register | Varies by jurisdiction |
A fake professional is caught immediately — anyone can look up whether the licence number corresponds to a real, active practitioner. Clients can automate this check where registers expose APIs.
A new verifier needs vouches from verified professionals in other fields. A corrupt doctor needs corrupt friends who are lawyers, notaries, or accountants. Cross-profession collusion rings are exponentially harder to build than single-profession ones.
Minimum requirement for verifier activation:
- At least 2 vouches from verified professionals
- At least 2 different professions represented
- Vouchers must themselves be active, non-revoked verifiers
All credentials are public Nostr events. Issuance volume is visible to the entire network.
Clients perform statistical anomaly detection:
- Volume flags: A doctor issuing 200 verifications/week vs. the network average of 5/week is automatically flagged.
- Geographic flags: A solicitor in Manchester verifying 50 families in Tokyo is suspicious.
- Temporal flags: 30 verifications in one hour suggests rubber-stamping, not in-person ID checks.
- Display: Clients show a warning badge on credentials from flagged verifiers: "This verifier's issuance pattern is unusual."
This is purely client-side — no central authority decides what's suspicious. Each client applies its own heuristics. The transparency of Nostr events makes this possible without surveillance.
Professional verifiers stake sats via Lightning when registering as a verifier. The bond is:
- Locked when the verifier attestation (kind 31000 with
type: verifier) is published - Slashed (burned or redistributed to reporters) if the verifier is found fraudulent and revoked
- Returned if the verifier deactivates cleanly (retires, leaves the network)
Bond amount is configurable per community policy. A casual group might require 100,000 sats. A high-security community might require 1,000,000 sats. The bond makes corruption financially costly — a corrupt verifier doesn't just lose their reputation, they lose money.
Implementation: NWC (Nostr Wallet Connect) for bond locking. The bond mechanism is optional — communities that don't require it simply don't set a bond threshold in their policy (kind 30078).
Anyone can publish a challenge attestation (kind 31000 with type: challenge) against a verifier, presenting evidence of fraudulent behaviour.
Challenge flow:
Reporter ──── publishes kind 31000 (type: challenge) ────► challenge attestation
│
includes evidence
(screenshots, registry status,
anomaly data, testimony)
│
◄──── community reviews ──────┘
│
If N trusted accounts (Tier 3+) confirm:
│
├─ Verifier's kind 31000 verifier attestation is superseded
│ by a revocation attestation (kind 31000, type: revocation)
│
├─ Lightning bond is slashed
│
└─ All credentials issued by this verifier
are flagged in clients
The threshold for revocation (how many confirmations needed) is set per community policy. Default: 5 confirmations from Tier 3+ accounts.
Every credential attestation (kind 31000 with type: credential) traces back to its issuer via the pubkey field. This is an immutable, public audit trail on Nostr relays.
If a verifier is revoked:
- Clients display a warning on all credentials they issued: "This verification was issued by a now-revoked verifier. Re-verification recommended."
- The credentials are not automatically invalidated — the community policy decides whether to require re-verification or grandfather existing credentials.
- The subject can get re-verified by a different professional. The old credential remains as a historical record.
A corrupt professional in this system must:
- Be genuinely registered with a professional body (or be caught instantly by registry check)
- Convince professionals in other fields to vouch for them (cross-profession requirement)
- Keep issuance volume low enough to avoid anomaly detection (limits the profit from corruption)
- Risk losing their Lightning bond (direct financial cost)
- Risk community reporting, public revocation, and permanent reputation damage on Nostr
- Accept that every fake credential they ever issued is permanently traceable back to them
Compare this to centralised identity verification: a company scans your ID, stores it in a database that gets breached, and has no accountability when it fails. Signet doesn't prevent all corruption — nothing does — but it makes corruption more expensive and more detectable than any centralised alternative.
Signet uses two Nostr event kinds for all identity attestations:
- Kind 31000 — Generic Verifiable Attestation (NIP-VA, NIP-VA Verifiable Attestations). All 7 identity attestation types share this kind, differentiated by the
typetag. - Kind 30078 — NIP-78 App-specific Data. Used for community verification policies.
Voting kinds (30482-30484) are documented in spec/voting.md.
All attestation events use kind 31000 with the following common tags:
["type", "<attestation_type>"]— one of:credential,vouch,verifier,challenge,revocation,identity-bridge,delegation["d", "..."]— d-tag format depends on the attestation pattern (see below)["summary", "<human-readable description>"]— brief description of the attestation["algo", "secp256k1"]— cryptographic algorithm["L", "signet"]— protocol namespace label
Each attestation type uses the NIP-VA pattern that matches its speech act:
- Assertion-first (Tier 2-4 credentials): The subject publishes a Tier 1 self-declaration. The verifier references it via
etag with"assertion"marker. d-tag:assertion:<tier-1-event-id>. Thetype: credentialtag is included as a hybrid override for relay filtering and resilience. - Direct claim (vouches, challenges): The attestor originates the claim. d-tag:
<type>:<subject-pubkey>. - Self-attestation (Tier 1, verifier registration): The publisher is also the subject. d-tag:
<type>:<own-pubkey>.
Tier 1 is a self-declaration (direct claim, pubkey equals p tag):
Tier 3 uses the assertion-first hybrid pattern, referencing the subject's Tier 1 self-declaration:
{
"kind": 31000,
"pubkey": "<verifier_pubkey>",
"tags": [
["d", "assertion:<tier-1-event-id>"], // assertion-first d-tag
["e", "<tier-1-event-id>", "wss://relay.example.com", "assertion"], // references Tier 1
["type", "credential"], // hybrid: explicit type for filtering
["p", "<subject_pubkey>"],
["tier", "3"],
["verification-type", "professional"],
["scope", "adult"],
["method", "in-person-id"],
["profession", "solicitor"],
["jurisdiction", "GB"],
["expiration", "<unix_timestamp>"],
["summary", "professional verification (tier 3) for abc123..."],
["algo", "secp256k1"],
["L", "signet"]
],
"content": "<zkp_proof_blob>"
}A replaceable event published by a peer vouching for another user.
{
"kind": 31000,
"pubkey": "<voucher_pubkey>",
"tags": [
["d", "vouch:<subject_pubkey>"], // type-prefixed d-tag
["p", "<subject_pubkey>"],
["type", "vouch"], // attestation type
["method", "in-person"], // "in-person" or "online"
["context", "bitcoin-meetup"], // optional: where/how they met
["voucher-tier", "3"], // voucher's own tier at time of vouch
["voucher-score", "87"], // voucher's own score at time of vouch
["summary", "in-person vouch for abc123..."],
["algo", "secp256k1"], // cryptographic algorithm (§9.5)
["L", "signet"],
["l", "vouch", "signet"]
],
"content": "" // no personal data
}A NIP-78 app-specific data event published by a community operator defining minimum verification requirements. Uses kind 30078 with a signet:policy: d-tag prefix.
{
"kind": 30078,
"pubkey": "<community_operator_pubkey>",
"tags": [
["d", "signet:policy:<community_identifier>"],
["adult-min-tier", "2"],
["child-min-tier", "3"],
["min-score", "50"], // optional minimum score
["mod-min-tier", "3"], // optional moderator requirement
["enforcement", "client"], // "client", "relay", or "both"
["verifier-bond", "100000"], // optional: min sats bond for verifiers
["revocation-threshold", "5"], // optional: confirmations needed to revoke
["algo", "secp256k1"] // cryptographic algorithm (§9.5)
],
"content": "<human-readable policy description>"
}A replaceable event published by a professional declaring their verifier status.
{
"kind": 31000,
"pubkey": "<verifier_pubkey>",
"tags": [
["d", "verifier"],
["type", "verifier"], // attestation type
["profession", "solicitor"],
["jurisdiction", "UK"],
["licence", "<encrypted_or_hashed_licence_number>"],
["body", "Law Society of England and Wales"],
["summary", "solicitor verifier in UK"],
["algo", "secp256k1"], // cryptographic algorithm (§9.5)
["L", "signet"],
["l", "verifier", "signet"]
// Cross-verification vouches from other professionals
// are separate vouch attestation events pointing at this pubkey
],
"content": "<optional: public statement about verification services>"
}An event published by anyone challenging a verifier's legitimacy. Triggers community review.
{
"kind": 31000,
"pubkey": "<reporter_pubkey>",
"tags": [
["d", "challenge:<verifier_pubkey>"], // type-prefixed d-tag
["p", "<verifier_pubkey>"],
["type", "challenge"], // attestation type
["reason", "anomalous-volume"], // "anomalous-volume", "registry-mismatch",
// "fraudulent-attestation", "licence-revoked",
// "other"
["evidence-type", "registry-screenshot"], // type of evidence provided
["reporter-tier", "3"], // reporter's own tier
["summary", "Challenge against verifier abc123... (anomalous-volume)"],
["algo", "secp256k1"], // cryptographic algorithm (§9.5)
["L", "signet"],
["l", "challenge", "signet"]
],
"content": "<detailed evidence and explanation>"
}A replaceable event published when a community confirms a challenge.
{
"kind": 31000,
"pubkey": "<revoking_authority_pubkey>", // community operator or threshold of Tier 3+ accounts
"tags": [
["d", "revocation:<verifier_pubkey>"], // type-prefixed d-tag
["p", "<verifier_pubkey>"],
["type", "revocation"], // attestation type
["challenge", "<challenge_event_id>"], // the challenge that triggered this
["confirmations", "7"], // number of Tier 3+ accounts that confirmed
["bond-action", "slashed"], // "slashed", "returned", "held"
["scope", "full"], // "full" = all credentials flagged,
// "partial" = specific credentials flagged
["effective", "<unix_timestamp>"], // when revocation takes effect
["summary", "Revocation of verifier abc123..."],
["algo", "secp256k1"], // cryptographic algorithm (§9.5)
["L", "signet"],
["l", "revocation", "signet"]
],
"content": "<summary of findings>"
}Client behaviour on revocation:
- Display a warning on all credentials issued by the revoked verifier
- Reduce the Signet Score contribution of those credentials to zero
- Notify affected users that re-verification is recommended
- Do not automatically invalidate credentials — the community policy decides whether to require re-verification or grandfather existing ones
A replaceable event published by an anonymous account to prove it is controlled by a verified identity, without revealing which one. Uses SAG ring signatures over secp256k1.
Use case: A user has a real-name account (verified at Tier 3 by a professional) and an anonymous account. The identity bridge lets the anonymous account prove "I am a real verified person" without revealing which verified person. This enables anonymous participation with trust.
{
"kind": 31000,
"pubkey": "<anon_pubkey>", // the anonymous account publishing this
"tags": [
["d", "identity-bridge"],
["type", "identity-bridge"], // attestation type
["ring-min-tier", "3"], // minimum verification tier of ring members
["ring-size", "10"], // number of pubkeys in the ring
["algo", "secp256k1"], // cryptographic algorithm (§9.5)
["L", "signet"],
["l", "identity-bridge", "signet"]
],
"content": "{\"ringSig\":{\"ring\":[...],\"c0\":\"...\",\"responses\":[...],\"message\":\"signet:identity-bridge:<anon_pubkey>:<timestamp>\"},\"timestamp\":...}"
}Ring signature construction:
- The real verified account's pubkey is placed at a random position in a ring of N verified pubkeys (minimum N=5).
- The binding message is
signet:identity-bridge:<anon_pubkey>:<timestamp>. - The real account's private key signs this message using a SAG ring signature, proving one of the N ring members also controls the anon account.
- The Nostr event itself is signed by the anonymous account's private key.
Verification:
- Verify the Nostr event signature (anon account signed the event).
- Parse the ring signature from content.
- Verify the binding message format matches
signet:identity-bridge:<event.pubkey>:<timestamp>. - Verify the ring signature (one of the ring members signed the binding message).
- Verify ring size >= 5 (anonymity threshold).
Signet Score contribution:
- Base weight: 50 points (between professional verification and in-person vouch).
- Scaled by ring minimum tier:
weight = 50 × (ringMinTier / 4). - Tier 3 ring → 37.5 points. Tier 4 ring → 50 points.
- Only one bridge per account is counted.
Trust compounding: When bridged anonymous accounts vouch for each other, each vouch is weighted by the voucher's score (which includes bridge points). This creates natural trust compounding without a special mechanism.
Privacy guarantees:
- The ring signature reveals only that one of N verified accounts controls the anon account, not which one.
- Minimum ring size of 5 provides meaningful anonymity (1-in-5 or better).
- Larger rings provide stronger anonymity at no additional cost to the verifier.
- The binding message prevents ring signature reuse across different anon accounts.
The protocol design (tiers, scores, event kinds, policies) is specified independently of the ZKP implementation. The credential format works regardless of which cryptographic library backs the proofs. This section specifies the recommended architecture.
Layer 1: Schnorr — the base (zero new dependencies)
│
├─ Credential issuance & verification
│ Verifier signs kind 31000 credential attestation with their Nostr key.
│ Any client verifies with schnorr.verify().
│
├─ Selective disclosure via Merkle trees
│ Credential attributes as Merkle leaves, sign root.
│ Holder reveals chosen attributes + sibling paths.
│ @noble/secp256k1 + @noble/hashes (already in every Nostr client).
│
├─ Ring signatures for issuer privacy (Tier 3/4)
│ SAG: "one of these N professionals verified this"
│ without revealing which one.
│ Stays on secp256k1.
│
├─ MuSig2 for multi-verifier co-signing
│ Multiple professionals co-sign → single aggregated Schnorr sig.
│
└─ Signet Score computation
Pure client-side math. Count vouches, weight by voucher score.
No crypto needed.
Layer 2: Pedersen range proofs — targeted addition (for Tier 4 age proofs)
│
└─ Age range proofs
"This child is in age range [8, 12]" without revealing exact age.
Pedersen commitment + Pedersen range proof range proof on secp256k1.
~700 byte proofs. No trusted setup. No new curve.
Layer 3: General-purpose ZK (future, if needed)
│
├─ Complex threshold proofs
│ "I have N vouches from tier 2+ accounts" via recursive composition.
│
├─ Credential binding proofs
│ Prove credential ownership + attribute predicate in one proof.
│
└─ Unlinkable presentations (if required)
Re-randomisable proofs. For when threat model evolves.
| Tier | Crypto Required | New Dependencies |
|---|---|---|
| Tier 1 (self-declared) | Standard Nostr event signing | None (already present) |
| Tier 2 (web-of-trust) | Vouch events + score calculation | None |
| Tier 3 (professional, adult) | Ring signature on credential | Ring signature library (secp256k1-based) |
| Tier 4 (professional, adult+child) | Ring sig + age range proof | Ring sig + Pedersen range proofs library |
| Blue checkmark / score | Read existing events, compute score | None |
Verifiers sign credentials with their Nostr key (secp256k1 Schnorr). No second keypair required.
| Approach | Pro | Con | Decision |
|---|---|---|---|
| Nostr key only | One identity, Nostr-native, no extra keys | Expensive to prove inside ZK circuits if needed later | Use this. |
| ZK-friendly curve (Baby Jubjub) | Cheap to prove in ZK circuits | Verifiers need a second keypair | Defer unless ZK circuit proofs become essential. |
Ring signatures handle issuer privacy without ZK circuits. Pedersen range proofs handle age range proofs without leaving secp256k1. The ZK-friendly signature question only arises if the protocol needs to prove "this Nostr key signed this credential" inside a zero-knowledge circuit — and the current design avoids that need.
| Library | Purpose | Notes |
|---|---|---|
@noble/secp256k1 |
Core Schnorr sign/verify | Stable, audited, 4.94KB gzipped |
@noble/hashes |
SHA-256 for Merkle trees | Stable, audited |
| Ring signature lib (e.g. Nostringer) | SAG on Nostr pubkeys | Experimental — needs audit before production |
| MuSig2 lib | Multi-party signing | Functional, BIP-327 |
| Pedersen range proofs lib | Range proofs on secp256k1 | Needs audit before production |
Every Signet event carries an ["algo", "secp256k1"] tag identifying the asymmetric cryptographic algorithm used for event signing and key agreement. This tag serves two purposes:
- Forward compatibility. When post-quantum algorithms (e.g. ML-DSA, SLH-DSA, ML-KEM) are standardised for Nostr, new events will carry a different
algovalue. Parsers can distinguish pre- and post-quantum events without heuristics. - Graceful migration. During a transition period, clients can accept both
secp256k1and post-quantum algorithm values, enabling a rolling upgrade across the network.
Rules for the algo tag:
| Rule | Detail |
|---|---|
| MUST be present on all Signet attestation events (kind 31000) | Builders set it to DEFAULT_CRYPTO_ALGORITHM (secp256k1) |
Parsers MUST default to secp256k1 if absent |
Ensures backward compatibility with legacy events created before this tag was introduced |
| Value is a free-form string | Allows future algorithms without a protocol revision — just publish events with the new value |
| One algorithm per event | An event is signed under a single algorithm; hybrid constructions would use two separate events |
Current status: All Signet events use secp256k1 (Schnorr signatures via BIP-340). No post-quantum migration is currently planned, but the tagging ensures the protocol is ready when one becomes necessary.
- Ring signature audit. Ring signatures are critical for Tier 3/4 issuer privacy. The SAG math is sound (used by Monero for years) but JS implementations need audit.
- Pedersen range proofs library choice. Pure JS (slower, easier to audit) vs WASM (faster, harder to audit the C layer).
- Ring size. How many professional verifiers form the anonymity set? Target: 20-50 verifiers per profession per jurisdiction.
- Credential expiry. Verification credentials should expire (recommended: 1-2 years) and be renewable.
- Revocation propagation. NIP-09 deletion requests are advisory only. The kind 31000 challenge/revocation attestation mechanism handles this at the protocol level.
The verification system is not limited to child safety. It is a general-purpose Nostr identity layer.
They can still build trust through:
- Peer vouches from verified users
- In-person key signings at meetups and conferences
- Account age and consistent behaviour
- Online vouches from high-IQ accounts
This gives them a visible Signet IQ and a checkmark — weaker than professional verification, but more than nothing. And nothing is what everyone on Nostr has today.
Already-doxed individuals (podcasters, conference speakers, known community members) can leverage their public identity. Their checkmark is backed by the fact that hundreds of people have met them and can vouch. They become trust anchors for their communities.
A verified creator can prove they're real without revealing their legal name. Content gated by verification tiers becomes more valuable when the creator has a verified identity backing it.
| Display | Meaning |
|---|---|
| No mark | Unverified (Tier 1) |
| ✓ | Web-of-trust vouched (Tier 2) |
| ✓✓ | Professional verified, adult (Tier 3) |
| ✓✓✓ | Professional verified, adult + child (Tier 4) |
| Signet IQ | Continuous identity quality (0–200) visible on drill-down |
| Standard | Relevance |
|---|---|
| NIP-58 (Badges, kind 30009/8) | Existing attestation system on Nostr. Signet credentials extend this pattern with cryptographic proofs. |
| NIP-101 (proposed) | Decentralised trust system integrating W3C VC format with Nostr. Signet is complementary — simpler, Nostr-native, focused on progressive tiers. |
Nostr DID method (did:nostr:<hex_pubkey>) |
Enables Nostr keys to participate in W3C DID/VC ecosystem. Signet credentials could be wrapped as W3C VCs for interop. |
| SchnorrSecp256k1Signature2019 (DIF) | Proposed proof type for W3C VCs using Schnorr over secp256k1. Would let Signet events be treated as standards-compliant W3C VCs. |
| ISO/IEC 27566-1:2025 | Age assurance standard. Signet's architecture is compatible with the outcomes-based framework (effectiveness, privacy, security, acceptability). |
| UK DIATF | Digital Identity and Attributes Trust Framework. Signet's Tier 3/4 architecture aligns. Not yet certified. Certification path exists. |
| EU eIDAS 2.0 | European digital identity framework. Signet's selective disclosure via Merkle trees and ZKP proofs aligns with eIDAS requirements for minimal disclosure. |
Signet is designed to satisfy identity verification requirements across multiple jurisdictions without centralised data collection.
| Regulation | How Signet Addresses It |
|---|---|
| UK Online Safety Act (ss.9, 11, 12) | Tier 4 provides "highly effective age assurance" via professional in-person verification. Exceeds Ofcom's accepted methods. No centralised ID scanning. |
| COPPA / COPPA 2.0 (US) | Cryptographic proof of parental consent via Tier 3/4 credential. Exceeds current parental consent mechanisms. The FTC's March 2026 policy statement permits data collection solely for age verification with robust deletion — Signet goes further by collecting no personal data at all. The ZKP proof contains zero PII. |
| EU eIDAS 2.0 | Credential format compatible with eIDAS selective disclosure direction. Could accept EU digital identity wallet credentials as Tier 3 input. |
| France SREN law | ZKP-based age verification is explicitly what the law requires. |
| Australia under-16 ban | Tier 4 proves child's age range without central verification. |
| ISO/IEC 27566-1:2025 | Protocol design is compatible with the outcomes-based framework. |
In March 2026, the FTC issued a policy statement signalling flexibility on COPPA age checks: it will not take enforcement action where personal data is collected solely for age verification purposes, provided the data is robustly deleted and clear notice is given to parents and children.
This is significant because it removes a major blocker — platforms were previously reluctant to implement age verification for fear of COPPA liability for collecting the verification data itself. The FTC is now saying: verify ages, just delete the data.
Signet leapfrogs this entirely:
| FTC Requirement | Signet's Position |
|---|---|
| Data collected solely for age verification | No data collected — ZKP proves age range without transmitting personal data |
| Robust data deletion | Nothing to delete — no PII ever enters any system |
| Clear notice to parents/children | Parent initiates the entire verification process and holds all keys |
| Don't use data for other purposes | Cryptographically impossible — the proof contains no personal data to repurpose |
The FTC lowered the bar. Signet doesn't need the bar — it flies over it.
Every other approach to age verification involves a trade-off: privacy vs safety, user experience vs verification rigour, centralised control vs accountability. Signet is designed to avoid that trade-off:
- Professional verifies in person → ZKP credential
- Credential proves age range without revealing personal data
- No personal data stored centrally — no database to breach
- Anti-corruption layers on verifier network
- Audit trail on Nostr relays
| Limitation | Why It's Acceptable |
|---|---|
| A verified parent can still be a bad parent | True of all systems. The protocol proves identity, not character. But it creates accountability — the parent's verified identity is cryptographically linked. |
| Professional verifiers could collude | Possible but career-ending. Cross-verification and professional body oversight mitigate this. Same risk exists in the existing notary/legal system. |
| Doesn't prevent all predators | A determined predator with a real child could still misuse the system. But they can no longer be anonymous — their Tier 4 credential links back to a professional who verified them in person. |
| ZKP crypto is complex | Implementation risk. Mitigated by using proven libraries and by designing the protocol independently of the crypto stack. |
| Adoption requires verifiers | Chicken-and-egg: users need verifiers, verifiers need users. Mitigated by the web-of-trust tier (no professionals needed) and by starting with existing professional networks. |
Signet is designed as a standalone NIP. Any Nostr client can implement it independently. The protocol does not depend on any specific client.
Clients implementing Signet can integrate credentials with:
- Parent-controlled vault tiers (cryptographic access control)
- Child account protection (NIP-26 delegation)
- Community verification policies
- Age-based permission defaults
Signet is open source. Contributions, feedback, and protocol discussion are welcome.
- Protocol specification: this document
- TypeScript library:
signet-protocolon npm - Attestation kind: Kind 31000 is used for all Signet identity attestations (credentials, vouches, verifier registration, challenges, revocations, identity bridges, delegation). Kind 30078 (NIP-78) is used for community policies. The voting extension uses kinds 30482–30484.
Signet identities are derived using nsec-tree, a hierarchical key derivation library for Nostr. A master secret is established from one of two sources, then child personas are derived deterministically from it.
Establishing the master secret:
Two entry points are supported:
-
From a BIP-39 mnemonic (
fromMnemonic()): Generate a 12-word BIP-39 mnemonic (128 bits of entropy + 4-bit checksum, encoded as 12 English words). The mnemonic is converted to a 64-byte seed via PBKDF2-SHA512, then BIP-32 HD derivation at pathm/44'/1237'/727'/0'/0'produces the master private key. The 12 words remain the human-readable backup for this flow. -
From an existing nsec (
fromNsec()): Users with an existing Nostr private key may import it as the master secret. An HMAC-SHA256 separation layer is applied so that the master material differs from the raw private key before any child keys are derived.
Option A (new identity):
BIP-39 mnemonic (12 words)
→ PBKDF2-SHA512 seed
→ BIP-32 HD path m/44'/1237'/727'/0'/0'
→ master secret
Option B (existing nsec):
nsec (NIP-19 bech32-encoded private key)
→ HMAC-SHA256 separation layer
→ master secret
Required personas:
Every Signet identity MUST derive two personas from the master secret:
| Persona | Purpose string | Role |
|---|---|---|
natural-person |
nostr:persona:natural-person |
Real-identity credentials; receives professionally verified attestations |
persona |
nostr:persona:persona |
Anonymous alias; receives age-range-only credentials from the two-credential ceremony |
Clients MAY derive additional personas using the purpose pattern nostr:persona:{name} for application-specific contexts.
Child derivation:
Each persona is derived from the master secret using HMAC-SHA256 with the following construction:
HMAC-SHA256(
key = master_secret,
data = "nsec-tree\0" || purpose_string || "\0" || index_be32
)
Where index_be32 is the derivation index encoded as a big-endian uint32. If the HMAC output is not a valid secp256k1 scalar (i.e. it is zero or ≥ the curve order), the index is incremented and the derivation is retried. The maximum index is MAX_INDEX = 0xFFFFFFFF.
The derived scalar becomes the child private key; the corresponding Schnorr x-only public key is the child's Nostr identity.
Optional passphrase: When deriving from a mnemonic, an optional passphrase adds a second factor (the "25th word") — a different passphrase produces an entirely different master secret and identity tree.
Because all personas are derived from a single master secret, the master owner can prove cryptographic ownership of any child identity without revealing the derivation relationship to observers.
Two proof types are defined:
| Proof type | What it contains | Privacy |
|---|---|---|
| Blind proof | BIP-340 Schnorr signature by the master key over the child pubkey | No derivation metadata — only proves master owns child |
| Full proof | Same Schnorr signature + purpose string + derivation index | Reveals the purpose and index to the verifying party |
Construction:
blind_proof:
message = SHA-256("signet-linkage-v1\0" || child_pubkey_bytes)
signature = schnorr_sign(master_private_key, message)
proof = { masterPubkey, childPubkey, signature }
full_proof:
message = SHA-256("signet-linkage-v1\0" || child_pubkey_bytes)
signature = schnorr_sign(master_private_key, message)
proof = { masterPubkey, childPubkey, purpose, index, signature }
Where proofs are used:
Linkage proofs are application-layer data. They are exchanged within bilateral flows — for example, inside the challenge (["payload", ...] tag of a kind 31000 challenge attestation) and response (kind 31000 revocation attestation) events of a challenge-response flow. They are not published as standalone Nostr events, ensuring privacy by default. A relying party who receives a proof can verify it locally without any relay interaction.
Verification:
Reconstruct the message from child_pubkey_bytes, then verify the Schnorr signature against master_pubkey. A valid signature proves the holder of the master private key authorised the child identity.
Writing down a master secret (mnemonic or private key bytes) creates a single point of failure. Shamir's Secret Sharing solves this by splitting the master secret into N shares where any M can reconstruct it.
Scheme: GF(256) polynomial interpolation (same field used by AES).
Default: 2-of-3 — keep one share, give two to trusted people. Any two of the three shares reconstruct the original master secret. No individual share reveals anything about the secret.
Share encoding: Each share is encoded as BIP-39 words (same wordlist, same format) so they look and feel familiar. A share holder sees 12 words that look like a normal mnemonic — but they are mathematically useless without a second share.
What is backed up: The master secret — either the mnemonic entropy (for fromMnemonic() users) or the private key bytes (for fromNsec() users). The backup mechanism is identical in both cases; only the source material differs.
Option A (mnemonic user):
[12-word mnemonic entropy] ← master secret
│
Shamir's 2-of-3 split
│
┌─────────┼─────────┐
│ │ │
Share 1 Share 2 Share 3
Keep Give to Give to
this friend A friend B
Any 2 shares → reconstruct → mnemonic entropy → master secret → all personas
Option B (nsec user):
[private key bytes] ← master secret
│
Shamir's 2-of-3 split
│
┌─────────┼─────────┐
│ │ │
Share 1 Share 2 Share 3
Any 2 shares → reconstruct → private key bytes → master secret → all personas
Parameters:
- Threshold (M): minimum 2, configurable
- Shares (N): minimum M, maximum 255, configurable
- Practical recommendation: 2-of-3 for personal use, 3-of-5 for high-value keys
When two Signet users meet in person, they establish a cryptographic connection by exchanging QR codes.
Connection flow:
Alice's phone Bob's phone
───────────── ──────────
1. Shows QR code containing: 1. Shows QR code containing:
- Alice's public key - Bob's public key
- Random nonce - Random nonce
- Selected contact info - Selected contact info
(name, mobile, etc.) (name, mobile, etc.)
2. Scans Bob's QR 2. Scans Alice's QR
3. Computes: 3. Computes:
ECDH(alice_priv, bob_pub) ECDH(bob_priv, alice_pub)
│ │
└──── Same shared secret S ────────────┘
4. Stores: 4. Stores:
- Bob's pubkey - Alice's pubkey
- Shared secret S - Shared secret S
- Bob's shared info - Alice's shared info
- What Alice shared - What Bob shared
ECDH shared secret derivation:
- Nostr public keys are x-only (32 bytes). Prepend
02to assume even y-coordinate. - Multiply the full point by the other party's private key scalar.
- SHA-256 hash the x-coordinate of the resulting point → 32-byte shared secret.
- The result is symmetric:
ECDH(A_priv, B_pub) === ECDH(B_priv, A_pub).
Contact info selection: During the QR exchange, each user selects what to share:
- Name
- Mobile number
- Home address
- Children's public keys (for families whose kids play together)
This data is stored locally only — never published to relays. The QR exchange happens entirely in person.
Automatic vouch: The connection optionally triggers a mutual in-person vouch attestation (kind 31000 with type: vouch), contributing to both users' Tier 2 web-of-trust and Signet Score.
The core peer-to-peer identity verification feature. Given a connection with a shared secret, both parties can independently compute the same words at any time — proving they hold the keys that established the connection.
The problem: Your friend calls, panicked, asking you to send money to a new bank account. It sounds like him — social cues are right, he drops the kids' names. But is it really him?
The solution: "Signet me." Both users open each other's profiles. Both see the same words. The caller reads them out. Match → it's really them.
Algorithm (powered by canary-kit):
Signet delegates word derivation to the CANARY protocol for protocol alignment. Canary handles real-time spoken verification; Signet handles identity and trust.
Inputs:
S = shared secret (32 bytes, from ECDH at connection time)
t = current Unix timestamp in milliseconds
N = word count (1-16, default: 3)
I = epoch interval in seconds (default: 30)
T = tolerance in epochs (default: 1)
Epoch:
E = floor(t / (I × 1000))
Derivation (CANARY-DERIVE):
H = HMAC-SHA256(S, utf8("signet:verify") || E_be32) // 32-byte MAC
Word extraction (N × 16-bit indices from H):
For i = 0 to N-1:
index = readUint16BE(H, i × 2) mod 2048
word[i] = CANARY_WORDLIST[index]
Wordlist: The Canary spoken-clarity wordlist (2048 words, curated from BIP-39 with homophones and phonetic near-collisions removed). Optimised for verbal exchange.
Defaults: 3 words, 30-second epoch, ±1 tolerance. These are the recommended settings for in-person and phone verification. Implementations MAY allow configuration of all three parameters for different use cases.
Configuration guidelines:
| Use Case | Words | Epoch | Tolerance | Effective Window |
|---|---|---|---|---|
| In-person verification | 3 | 30s | ±1 | 90 seconds |
| Phone call | 3 | 30s | ±1 | 90 seconds |
| Quick in-room check | 1 | 30s | ±1 | 90 seconds |
| Async/text channel | 3 | 300s | ±1 | 15 minutes |
| High-security | 4 | 30s | 0 | 30 seconds |
Entropy by word count:
| Words | Entropy | Combinations |
|---|---|---|
| 1 | 11 bits | 2,048 |
| 2 | 22 bits | ~4 million |
| 3 | 33 bits | ~8.6 billion |
| 4 | 44 bits | ~17.6 trillion |
Maximum word count: 16 (32 bytes of HMAC output / 2 bytes per word index).
Properties:
- Symmetric: Both parties compute the same words from the same shared secret.
- Rotating: Words change every epoch. Stale words cannot be replayed.
- Tolerant: Verification accepts current epoch ±T (default: ±1, giving a 3× epoch window) to accommodate lag.
- Offline: No server, no relay, no network needed. Pure local computation.
- Configurable: Word count, epoch interval, and tolerance are adjustable for different security/usability trade-offs.
- Consistent prefix: The first N words are always the same regardless of the total word count requested. Asking for 4 words gives the same first 3 as asking for 3.
- Protocol aligned: Uses canary-kit's CANARY-DERIVE primitive and spoken-clarity wordlist, ensuring consistent vocabulary across Signet (identity establishment) and Canary (ongoing verification, duress signalling).
Use cases:
| Scenario | Flow |
|---|---|
| Friend asks for money | "Signet me." Both open the app. Words match → send it. |
| Bank calls about suspicious activity | "Read me my signet." Agent checks your profile → reads the 3 words. |
| You call the bank | "What's my signet?" You verify they have access to the bank's private key. |
| Family emergency contact | Relative calls claiming to be with your child. "Signet me first." |
| Business verification | Supplier calls to change payment details. "Signet me before I update anything." |
Display:
┌──────────────────────────────────────┐
│ Bob's Profile │
│ │
│ My Signet: │
│ │
│ ocean · tiger · marble │
│ │
│ ████████████░░░░ 18s │
│ │
│ Tap to verify words read to you │
└──────────────────────────────────────┘
The progress bar shows time until the next word rotation. The user sees the words update in real time.
Key management:
- The master secret (mnemonic entropy for
fromMnemonic()users, private key bytes forfromNsec()users) must be stored offline (paper, metal backup). It is the root of the entire identity tree. - The optional passphrase (mnemonic flow) adds plausible deniability — a different passphrase produces an entirely different master secret and identity tree.
- Shamir shares should be distributed to people in different locations.
Shared secrets:
- ECDH shared secrets are derived from the connection and stored locally. They never leave the device.
- If a device is compromised, all shared secrets on that device are compromised. Users should re-establish connections from a new device.
Signet words:
- The epoch interval prevents replay beyond the tolerance window.
- An attacker who compromises one party's device gains their shared secrets and can generate words. This is equivalent to compromising their private key — the defence is device security, not protocol design.
- For high-value transactions, use 4+ words with tight tolerance (0), and combine with other verification (video call, previously agreed code phrase).
- Using 1 word (1-in-2,048) is acceptable only for low-stakes, physical-proximity scenarios where the threat model is impersonation, not brute force.
- Both parties MUST agree on the same configuration (word count, epoch, tolerance) for verification to succeed. Mismatched config will always fail.
nsec-tree derivation constraints:
MAX_INDEX = 0xFFFFFFFF— implementations MUST NOT attempt derivation beyond this index.- Curve-order skip: if the HMAC-SHA256 output for a given index is not a valid secp256k1 scalar (i.e. zero, or ≥ the curve order
n), increment the index by one and retry. This is equivalent to BIP-32 hardened child key derivation behaviour and avoids biased keys. - Memory cleanup: implementations MUST call
zeroise()on theUint8ArrayprivateKeyandpublicKeyfields ofIdentityobjects when they are no longer needed.TreeRoot.destroy()MUST be called to zeroise the root secret held in memory. - JS string limitation:
nsecandnpubstring fields onIdentityobjects cannot be wiped from memory — JavaScript string primitives are immutable and GC-managed. OnlyUint8Arrayfields (privateKey,publicKey) can be actively zeroed. Implementations should avoid materialisingnsec/npubstrings unless required by the application layer.
The identity bridge allows users to maintain separate anonymous and real-name accounts while cryptographically proving their anonymous account is backed by a verified identity. This is essential for privacy-respecting participation in communities that require trust.
Example flow:
- Alice has a real-name account verified at Tier 3 by her solicitor.
- Alice creates an anonymous account for participating in communities where she wants privacy.
- Alice creates an identity bridge: her real account signs a ring signature (among 10 other verified accounts) proving "one of these 11 people also controls this anon account."
- The identity bridge attestation (kind 31000 with
type: identity-bridge) is published from Alice's anon account. - Community members see Alice's anon account has a Signet Score of ~38 (from the bridge alone), indicating a verified person is behind it.
- When other bridged anonymous accounts vouch for Alice's anon account, trust compounds naturally.
The ring must contain at least 5 verified pubkeys (the MIN_BRIDGE_RING_SIZE constant). The user's real pubkey is placed at a random position among decoys selected from the pool of verified accounts on the relay.
Decoy selection:
- Query the relay for pubkeys with kind 31000 credential attestations at or above the desired minimum tier.
- Exclude the user's real pubkey from the candidate pool.
- Randomly select
ringSize - 1decoys. - Insert the real pubkey at a random index.
Users may import existing Nostr accounts via nsec (NIP-19 bech32-encoded private keys). When imported via fromNsec(), the private key bytes become the master secret after an HMAC-SHA256 separation layer is applied. nsec-imported accounts:
- Have no BIP-39 mnemonic, but CAN use Shamir backup (the private key bytes are the material that is split).
- Can fully participate in the protocol (vouch, receive credentials, create bridges).
- Can derive additional personas from the imported key as the master secret.
- Are clearly indicated in the UI as nsec-imported.
A device may hold multiple accounts (real-name + anonymous, or multiple identities). Each account:
- Is identified by its pubkey (not a singleton key).
- Has its own connections, credentials, and Signet Score.
- Can be switched between in the app UI.
Connections are scoped to the owning account. Credentials and vouches are naturally scoped by pubkey in the Nostr protocol.
The protocol classifies accounts along two orthogonal axes:
- Verification tier (1–4): How deeply an account is verified.
- Entity type (9 types): What kind of entity the account represents.
These axes are independent. A Natural Person could be Tier 3 or Tier 4. An Unlinked Agent is always Tier 1. Entity type is determined by the cryptographic mechanism that links the account to the chain of trust.
The protocol defines nine entity types in three root categories.
Root categories:
| Protocol Term | Code | Description |
|---|---|---|
| Natural Person | natural_person |
A living human, professionally verified (Tier 3+) |
| Juridical Person | juridical_person |
A legal entity, verified by professional + multi-sig from Natural Persons |
| Unlinked Agent | unlinked_agent |
An unverified account with no chain of trust (the default) |
Alias subtypes (anonymous identities):
| Protocol Term | Code | Description |
|---|---|---|
| Persona | persona |
Anonymous alias of a Natural Person, linked via identity bridge (ring signature) |
| Juridical Persona | juridical_persona |
Anonymous alias of a Juridical Person, linked via identity bridge |
Agent subtypes (delegated bots):
| Protocol Term | Code | Description |
|---|---|---|
| Personal Agent | personal_agent |
Bot delegated by a Natural Person |
| Unlinked Personal Agent | unlinked_personal_agent |
Bot delegated by a Persona |
| Organised Agent | organised_agent |
Bot delegated by a Juridical Person |
| Unlinked Organised Agent | unlinked_organised_agent |
Bot delegated by a Juridical Persona |
Naming convention: The protocol uses legal terminology (Natural Person, Juridical Person, Persona) for precision. Client applications SHOULD use friendly labels (Person, Organisation, Alias) in user interfaces. See §17.8 for the recommended label mapping.
Natural Person ──► Personal Agent
│
└──► Persona ──► Unlinked Personal Agent
Juridical Person ──► Organised Agent
│
└──► Juridical Persona ──► Unlinked Organised Agent
Unlinked Agent (standalone, no chain of trust)
Every account starts as an Unlinked Agent. Entity type is earned through the appropriate verification mechanism.
Entity type is defined by the cryptographic linkage that connects an account to the chain of trust:
| Entity Type | Linkage Mechanism | Event Kind |
|---|---|---|
| Natural Person | Professional credential | 31000 (type: credential, Tier 3/4) |
| Persona | Identity bridge (ring signature) from a Natural Person | 31000 (type: identity-bridge) |
| Personal Agent | Delegation event from a Natural Person | 31000 (type: delegation) |
| Unlinked Personal Agent | Delegation event from a Persona | 31000 (type: delegation) |
| Juridical Person | Professional credential + multi-sig attestation | 31000 (type: credential) |
| Juridical Persona | Identity bridge (ring signature) from a Juridical Person | 31000 (type: identity-bridge) |
| Organised Agent | Delegation event from a Juridical Person | 31000 (type: delegation) |
| Unlinked Organised Agent | Delegation event from a Juridical Persona | 31000 (type: delegation) |
| Unlinked Agent | None | — |
The existing credential attestation (kind 31000) gains a new tag:
["entity-type", "<type_code>"]Where <type_code> is one of: natural_person, persona, personal_agent, unlinked_personal_agent, juridical_person, juridical_persona, organised_agent, unlinked_organised_agent, unlinked_agent.
This tag is derived from the verification mechanism used but is included explicitly for relay and client queryability.
A replaceable event published by an account owner to delegate authority to an agent (bot).
{
"kind": 31000,
"pubkey": "<owner_pubkey>",
"tags": [
["d", "<unique_delegation_id>"],
["type", "delegation"], // attestation type
["p", "<agent_pubkey>"], // the bot/agent being delegated
["entity-type", "<agent_type>"], // personal_agent, unlinked_personal_agent,
// organised_agent, unlinked_organised_agent
["agent-type", "<type>"], // optional: "ai", "human", or "device"
["expires", "<unix_timestamp>"], // optional: delegation expiry
["algo", "secp256k1"], // cryptographic algorithm (§9.5)
["L", "signet"],
["l", "delegation", "signet"]
],
"content": ""
}Delegation constraints:
- A Natural Person may delegate →
personal_agent - A Persona may delegate →
unlinked_personal_agent - A Juridical Person may delegate →
organised_agent - A Juridical Persona may delegate →
unlinked_organised_agent
Any other owner→agent type combination is invalid and MUST be rejected by clients and relays.
Agent type tag (optional):
The ["agent-type", "<type>"] tag distinguishes what kind of agent is being delegated:
| Value | Meaning | Example |
|---|---|---|
ai |
An AI/software agent acting on behalf of the owner | Personal AI assistant, DVM operator, moderation bot |
human |
A human delegate (not an AI) | Guardian, employee, temporary access |
device |
A physical device (robot, IoT, telepresence) | Humanoid robot, smart lock, drone |
If omitted, clients SHOULD assume human for backwards compatibility. The tag is informational — it helps clients display appropriate trust context (e.g., "This is an AI agent operated by a verified person" vs "This is a human delegate").
Revocation: Delegations are revoked using a kind 31000 revocation attestation (with type: revocation), with the ["d", "<agent_pubkey>"] tag pointing to the agent being revoked.
A Juridical Person (organisation) requires dual verification:
- Professional verification: A Tier 3+ professional verifies the organisation's legal registration documents (articles of incorporation, registration certificate, etc.) and issues a credential attestation (kind 31000 with
type: credential). - Multi-sig attestation: N-of-M verified Natural Persons co-sign a credential attesting they represent the organisation (e.g., 3 of 5 board members). Each co-signer must themselves be a verified Natural Person.
Both proofs must be present for an account to achieve Juridical Person status.
An institution MAY publish its Signet identity and staff roster via https://<domain>/.well-known/signet.json. This extends the schema defined in §27.1 (Cold-Call Verification) with fields for entity type, registry cross-referencing, and staff pubkeys.
{
"version": 2,
"name": "Baker & Co Solicitors",
"entity": "juridical_person",
"registry": {
"authority": "sra",
"id": "654321",
"url": "https://www.sra.org.uk/consumers/register/organisation/?sraNumber=654321"
},
"pubkeys": [
{
"id": "firm-key-2026",
"pubkey": "<64-char hex secp256k1 x-only pubkey>",
"label": "Firm Verification Key",
"created": "2026-01-15T00:00:00Z"
}
],
"staff": [
{
"pubkey": "<64-char hex>",
"name": "Jane Smith",
"role": "solicitor",
"registry": { "authority": "sra", "id": "123456" }
},
{
"pubkey": "<64-char hex>",
"name": "John Davies",
"role": "solicitor",
"registry": { "authority": "sra", "id": "789012" }
}
],
"relay": "wss://relay.example.com",
"policy": {
"rotation": "annual",
"contact": "[email protected]"
}
}Version 2 fields (all optional — version 1 documents remain valid):
| Field | Type | Description |
|---|---|---|
entity |
string | Entity type code: juridical_person or juridical_persona |
registry |
object | Regulatory body and registration ID for the institution |
registry.authority |
string | Registry identifier: sra, gmc, gdc, arb, ofsted, companies-house, etc. |
registry.id |
string | The institution's registration ID on that registry |
registry.url |
string | Optional: direct URL to the institution's public registry entry |
staff |
array | Array of verified individuals at this institution |
staff[].pubkey |
string | 64-char hex secp256k1 x-only pubkey of the staff member |
staff[].name |
string | Display name (for human cross-referencing) |
staff[].role |
string | Role at the institution (e.g., solicitor, gp, head-teacher) |
staff[].registry |
object | Optional: the individual's own registry entry (e.g., their personal SRA ID) |
Validation rules (extending §27.1 rules):
versionMUST be1or2. Clients MUST accept version 1 documents (which lackentity,registry,staff).entity, if present, MUST bejuridical_personorjuridical_persona.staff, if present, MUST be an array with at most 500 entries.- Each
staff[].pubkeyMUST be a 64-character lowercase hexadecimal string. registry.authorityvalues SHOULD use the lowercase identifiers defined in the Signet registry authority table (see §17.7.4).- The document MUST NOT exceed 102,400 bytes (100 KB) to accommodate large staff rosters.
The recommended onboarding flow for an institution (e.g., a law firm):
Step 1: Create the institution's Signet identity.
The managing partner (or designated compliance officer) generates a BIP-39 mnemonic for the institution. This mnemonic is the institution's master key. It SHOULD be:
- Generated on a secure device (hardware wallet or air-gapped machine)
- Backed up via Shamir Secret Sharing (e.g., 2-of-3 split across partners) using
@forgesworn/shamir-words - Never stored on a general-purpose computer or cloud service
The mnemonic derives the institution's juridical_person identity via createSignetIdentity().
Step 2: Professional verification of the institution.
A Tier 3+ verified professional (who is NOT an employee of the institution) verifies:
- The institution's legal registration documents (e.g., Companies House certificate, SRA firm registration)
- That the persons claiming to represent the institution match their registry records
The professional issues a credential attestation (kind 31000, type: credential, entity-type: juridical_person) against the institution's pubkey.
Step 3: Multi-sig attestation by directors/partners.
N-of-M verified Natural Persons at the institution co-sign a credential attesting they represent the organisation. For example, for a law firm with 5 equity partners, a 3-of-5 attestation. Each co-signer must themselves be a verified Natural Person (Tier 3+).
Step 4: Publish .well-known/signet.json.
The institution publishes the domain proof file on its website. This anchors the institution's pubkey to its domain. For institutions on restricted domains (.sch.uk, .nhs.uk, .ac.uk, .gov.uk), the domain itself provides infrastructure-level anti-Sybil — the domain registrar has already verified the institution's legitimacy.
Step 5: Staff verification.
The institution (via its juridical_person identity) issues credentials to individual staff members. Each staff credential:
- Is issued by the institution's pubkey (not by the individual professional's own verification chain)
- Includes the
entity-type: natural_persontag on the staff member's pubkey - Includes a
["delegator", "<institution_pubkey>"]tag linking the credential to the institution - Can be cross-referenced against the staff member's individual registry entry (e.g., their personal SRA ID)
This means a solicitor at the firm has TWO credential paths:
- Via the institution: SRA register → firm's domain →
.well-known/signet.json→ firm credential → individual - Via personal verification: Another verified professional vouches for them in person
Either path is valid. Both together are stronger.
Step 6: Staff are listed in .well-known/signet.json.
The institution adds each verified staff member's pubkey to the staff array. This creates a single machine-readable file that lists every verified person at the institution, cross-referenceable against the relevant professional register.
When an institution issues a credential to a staff member, the credential event includes tags linking it to the institution:
{
"kind": 31000,
"pubkey": "<institution_pubkey>",
"tags": [
["d", "<unique_credential_id>"],
["type", "credential"],
["p", "<staff_member_pubkey>"],
["entity-type", "natural_person"],
["delegator", "<institution_pubkey>"],
["delegator-entity", "juridical_person"],
["registry", "sra", "<staff_member_sra_id>"],
["algo", "secp256k1"],
["L", "signet"],
["l", "credential", "signet"]
],
"content": ""
}The ["registry", "<authority>", "<id>"] tag enables automated cross-referencing. A verifying client can:
- Find the credential issued by
<institution_pubkey> - Fetch
https://<institution-domain>/.well-known/signet.json - Confirm
<institution_pubkey>matches a pubkey in the file - Confirm
<staff_member_pubkey>appears in thestaffarray - Look up the
registrytag value against the relevant professional register (e.g., SRA API) - Confirm the named person at the named registry ID works at the named firm
All six checks must pass for full verification. Steps 1–4 are automatable. Step 5 requires registry integration (API or web scrape). Step 6 is a human-readable cross-check.
The protocol defines the following registry authority identifiers for use in registry.authority fields and ["registry"] tags:
| Identifier | Registry | Country | Profession |
|---|---|---|---|
sra |
Solicitors Regulation Authority | UK | Solicitors |
bsb |
Bar Standards Board | UK | Barristers |
gmc |
General Medical Council | UK | Doctors/GPs |
gdc |
General Dental Council | UK | Dentists |
gphc |
General Pharmaceutical Council | UK | Pharmacists |
nmc |
Nursing and Midwifery Council | UK | Nurses, midwives |
goc |
General Optical Council | UK | Opticians, optometrists |
hcpc |
Health and Care Professions Council | UK | Physios, paramedics, psychologists, etc. |
swe |
Social Work England | UK (England) | Social workers |
arb |
Architects Registration Board | UK | Architects |
rcvs |
Royal College of Veterinary Surgeons | UK | Veterinarians |
icaew |
Institute of Chartered Accountants | UK | Chartered accountants |
acca |
Assoc. of Chartered Certified Accountants | UK | Chartered certified accountants |
rics |
Royal Institution of Chartered Surveyors | UK | Chartered surveyors |
engc |
Engineering Council | UK | Chartered engineers |
gtcs |
General Teaching Council for Scotland | UK (Scotland) | Teachers |
ofsted |
Office for Standards in Education | UK (England) | Schools, childminders, nurseries |
companies-house |
Companies House | UK | Companies, LLPs |
faculty-office |
Faculty Office of the Archbishop of Canterbury | UK | Notaries public |
This table is non-exhaustive. Implementations SHOULD accept any registry.authority value — unknown authorities are not invalid, merely unverifiable by that particular client. International registries may be added by any implementation.
Client applications SHOULD map protocol entity types to user-friendly labels:
| Protocol Term | Recommended App Label |
|---|---|
| Natural Person | Person |
| Persona | Alias |
| Personal Agent | Personal Agent |
| Unlinked Personal Agent | Unlinked Personal Agent |
| Juridical Person | Organisation |
| Juridical Persona | Org Alias |
| Organised Agent | Organised Agent |
| Unlinked Organised Agent | Unlinked Org Agent |
| Unlinked Agent | Unlinked Agent |
A single account (particularly a physical device such as a humanoid robot or telepresence system) may switch between entity types depending on who or what is in control at a given moment. Events published by such accounts SHOULD include a mode tag:
["mode", "<mode>"]Where <mode> is one of:
| Mode | Meaning |
|---|---|
teleoperated |
A verified person is in direct real-time control (entity acts as Persona) |
autonomous |
AI/software is acting on behalf of the owner (entity acts as Agent) |
assisted |
A verified person is in control with AI assistance (entity acts as Persona) |
Example: A paraplegic Natural Person controls a humanoid robot via a brain-computer interface. When the person is directly controlling the robot, it publishes events with ["mode", "teleoperated"] and operates as a Persona. When the person steps away and onboard AI takes over, it publishes with ["mode", "autonomous"] and operates as a Personal Agent. This allows others interacting with the robot to know whether they are communicating with the person or their AI.
This pattern applies to any remote-operated system: telepresence robots, drone operators, remote surgery rigs, VR avatars — any case where a verified person may or may not be in direct control at a given moment.
Community verification policies (kind 30078) may include entity type requirements:
["allowed-entity-types", "natural_person,persona,juridical_person"]Relays MAY filter events by entity type. For example, a child-safety relay might only accept events from Natural Persons and Personas (verified humans and their aliases), rejecting Unlinked Agents and unverified bots.
The current taxonomy covers entities that are human, human-controlled, human-organised, or unverified. It does not cover fully autonomous beings (e.g., a sentient AI or truly independent robot) that act on their own behalf rather than on behalf of a human or organisation.
If such entities require their own legal or social standing, a new root category — Synthetic Person — may be added alongside Natural Person and Juridical Person, with its own alias and agent subtypes. The taxonomy is designed to accommodate this without breaking changes.
Any identity protocol that achieves meaningful adoption will be evaluated — and potentially co-opted — by adversarial actors, including nation-states. A protocol that works only under cooperative assumptions is not a protocol; it is an invitation to abuse. Signet MUST be designed, evaluated, and maintained under adversarial assumptions, including the scenario where a government mandates Signet ID and then attempts to weaponise it.
This section defines the threat model, compares Signet's position to the current state and its trajectory, and establishes formal requirements that the protocol must satisfy.
The following table compares ten adversarial scenarios across three contexts: the current state (2026), the trajectory if centralised digital identity continues unchecked, and the position with Signet.
| Scenario | Current State (2026) | Trajectory Without Decentralised Alternative | With Signet |
|---|---|---|---|
| Identity revocation | Multiple ID forms exist. Losing one doesn't kill identity. | Centralised digital ID (EU eIDAS, UK DIATF) creates single points of failure. One revocation = locked out. India's Aadhaar locks people out of food rations. | Multiple independent credential issuers. No single revocation kills identity. Government revokes their attestation, not identity itself. |
| Anonymous participation | Possible via cash, physical post, in-person. Diminishing with CCTV, card-only payments, phone tracking. | Cash elimination. Real-name platform verification (EU DSA, UK OSA). Anonymous speech criminalised or de-platformed. Trajectory: zero anonymity. | Ring signatures provide mathematical anonymity. Persona accounts unlinkable to Natural Person. Anonymous but verified participation is a protocol feature, not a loophole. |
| Backdoored identity | Government holds biometrics. Centralised databases. Citizen doesn't control infrastructure. | Mandatory government wallet apps. Closed-source, government-audited. Apple/Google as gatekeepers. Must use their app to have identity. | Open-source clients. Keys generated offline. Open spec anyone can implement. No mandated app. |
| Retrospective de-anonymisation | ISP logging (UK IPA). CCTV retained. Much activity still unrecorded. | AI retroactive analysis of CCTV, social media, location, payments. "Reconstruct your 2025" becomes routine. Facial recognition on stored footage. | Risk exists (quantum). Post-quantum crypto migration is plannable. Analogue world has NO defence against retrospective AI analysis. |
| Mass surveillance | GCHQ/NSA bulk collection. Smart city sensors. Mobile tracking. Mostly passive. | Real-time AI monitoring. IoT everywhere. China-model normalised as "public safety." Active, not passive. | Relay diversity across jurisdictions. Encrypted connections. Signet words work offline. Can't surveil what doesn't touch your infrastructure. |
| Statistical de-anonymisation | Browser fingerprinting, ad IDs, metadata analysis already de-anonymise routinely. | AI correlation attacks improve exponentially. Pseudonymity provides zero real protection. | Ring signatures stronger than pseudonymity — provable unlinkability. Larger rings = stronger guarantees. |
| Social graph mapping | Social media, phone contacts, email, payments reveal relationships. Government can request with warrants. | AI real-time social graph analysis. Cross-platform graph merging. No relationship private. | Persona-based connections unlinkable to Natural Person. Government key and social key cryptographically separated. |
| Family structure exploitation | Birth certs, school records, tax returns link families. Government knows family structure. | Centralised child identity systems. Family graphs cross-linked across all services. | ZKP proves "parent has child aged 8-12" without revealing which child, which school, any detail. |
| Verifier coercion | Government can pressure professionals via licensing. Professionals have some legal protections. | Professional independence eroded. Licensing bodies politicised. "Comply or lose licence" routine. | Multiple verifiers across jurisdictions and professions. No single jurisdiction's coercion captures entire verification chain. |
| Election manipulation | Paper ballots work reasonably for secrecy. Postal voting vulnerable. No crypto guarantees. | Digital voting without proper crypto. Centralised "trust us" counting. Convenience over security. | Linkable ring signatures for ballot secrecy. Re-voting for coercion resistance. Verifiable tallying anyone can audit. |
Every defence follows the same meta-principle: decentralisation prevents single-entity control.
- Multiple credential issuers — no single entity's revocation kills your identity
- Open-source clients — no mandated app can be secretly backdoored
- Relay diversity — no single jurisdiction controls the communication layer
- Large ring signatures — statistical de-anonymisation requires infeasible computation
- Post-quantum migration path — build crypto agility into the spec now
- Multiple election authorities — no single signer controls ballot issuance
- Cross-jurisdiction verifiers — professionals in different countries provide independent trust paths
- Voluntary credential presentation — the protocol MUST NOT enable compulsory ID through the back door
The protocol MUST ensure that no single entity — including a nation-state — can unilaterally:
- Revoke a person's identity — only their own attestation, not the keypair or credentials from other issuers
- De-anonymise a Persona — without possession of the private key
- Coerce a vote — without detection by the voter or audit trail
- Surveil all activity — relay diversity defeats centralised monitoring
- Weaponise credentials as social credit — multiple independent issuers prevent any single issuer from gatekeeping participation
These properties are non-negotiable. Any proposed protocol change that weakens any of these guarantees MUST be rejected unless it provides an equivalent or stronger guarantee through a different mechanism.
Governments are simply another class of verifier in the Signet model. A government issues kind 31000 credential attestations to citizens, just as a professional verifier issues credentials to individuals. The critical difference from traditional national ID:
| Aspect | Traditional National ID | Signet ID |
|---|---|---|
| Who generates the identity | Government issues it | Citizen generates keypair |
| Who holds the master record | Government database | Citizen holds private key |
| What government stores | Name, DOB, address, photo, biometrics | Public key + attestation |
| Single point of failure | Government database breach = mass identity theft | No central database to breach |
| Revocation power | Government can cancel your identity | Government can revoke their attestation; your key still works with other verifiers |
| Surveillance capability | Full — they hold all your data | Limited — they hold a public key |
No new event kinds are required. Governments use kind 31000 for credential issuance (type: credential), revocation (type: revocation), and identity bridges (type: identity-bridge) where applicable, plus kind 30078 for policies.
Citizen verification follows a six-step process using existing protocol mechanisms:
- Key generation — Citizen generates a Nostr keypair using a 12-word BIP-39 mnemonic (via the nsec-tree
fromMnemonic()derivation path) - In-person appearance — Citizen visits a government office (analogous to passport issuance)
- Document verification — Government official verifies identity documents in person
- Credential issuance — Government issues a credential attestation (kind 31000 with
type: credential) to the citizen's pubkey: "This pubkey belongs to a verified citizen" - Connection establishment — Citizen and government official establish a verified connection (QR exchange, shared secret, or Signet words)
- Ongoing verification — For future interactions, either party can verify the other using "Signet me" time-based word verification
A government may issue a "good standing" credential — a kind 31000 credential attestation (with type: credential) that indicates the citizen has no outstanding warrants or court orders requiring action.
Revocation as warrant mechanism:
- When a court issues a warrant, the good standing credential is revoked via a kind 31000 revocation attestation (with
type: revocation) - The citizen's ZKP can prove non-membership in the revocation set (i.e., "I am not on any warrant list")
- Ring signatures anonymise which specific credential is being proven
- The credential can be re-issued when the warrant is resolved
Privacy guarantee: The revocation set is public (as all kind 31000 revocation attestations are), but ring signatures prevent observers from linking a specific proof of good standing to a specific citizen.
Current process:
- Police stop a person
- Ask for name
- Run name through Police National Computer / NCIC
- Check for warrants
- If clear, person goes — but name, location, and time are all logged
Signet process:
- Police stop a person
- Ask to verify status
- Person's device presents a ZKP: "I hold a valid, non-revoked citizen credential"
- Officer's device verifies the proof and checks revocation status
- If clear, person goes — no identity revealed
The officer learns exactly one fact: this person holds a valid, non-revoked citizen credential.
Critical constraint: Presentation of a Signet credential MUST be voluntary. Refusal to present a credential MUST NOT be grounds for detention, search, or further action. Without this constraint, Signet becomes compulsory ID through the back door. This requirement MUST be enshrined in enabling legislation (see §19.6).
Citizens maintain strict separation between their government-registered identity and their private life:
Citizen's registered pubkey (Natural Person)
│ Used ONLY for government interactions:
│ taxes, official correspondence, warrant checks
│
└──► Persona accounts (anonymous aliases)
│ Used for private life:
│ social media, communities, personal expression
│ Ring signature proves "I am a real citizen"
│ without revealing which one
│
└──► Unlinked Personal Agents
Bots/services under anonymous identity
The government knows the citizen's registered pubkey. They cannot link it to any Persona account without breaking the ring signature — which is computationally infeasible. This separation is enforced by cryptography, not by policy.
For civic identity to function as described, enabling legislation MUST ensure:
- Voluntary presentation — no person may be compelled to present a Signet credential outside of contexts where identification is already legally required (e.g., border control, court proceedings)
- Refusal without consequence — refusal to present a credential in a voluntary context must not be treated as grounds for suspicion, detention, or any adverse action
- Keypair sovereignty — the citizen's private key is their property; government may not demand access to it
- Credential plurality — citizens may hold credentials from multiple issuers; no single government credential may be made a prerequisite for participation in civil society
- Algorithmic audit — any government systems that process Signet credentials must be subject to independent audit
- Sunset and review — civic identity legislation must include mandatory review periods to assess whether the system is being used as intended
Anonymity is a first-class design goal, not an afterthought. Every person verified through Signet receives two credentials on two separate keypairs:
- Keypair A (Natural Person): Real identity — name, nationality, document hash, nullifier
- Keypair B (Persona): Anonymous identity — inherits only the age-range proof and guardian tags (if child)
The professional verifier sees both keypairs and issues both credentials in a single ceremony. The link between keypairs is known only to the subject and the verifier (protected by professional confidentiality — solicitor-client privilege, notary secrecy, medical confidentiality).
The two-credential ceremony follows these steps:
- Subject presents two Nostr pubkeys (keypair A and keypair B) to the verifier
- Subject presents identity documents (passport, national ID, birth certificate)
- Verifier examines documents and confirms identity of the person present
- A Merkle tree is built from verified attributes (name, nationality, document type, DOB, nullifier)
- Verifier computes nullifier using length-prefixed encoding (see §20.7)
- Verifier generates Pedersen range proof age-range proof from date of birth
- Verifier issues Natural Person credential (keypair A) with: entity-type, merkle-root, nullifier, age-range, guardian tags (if child)
- Verifier issues Persona credential (keypair B) with: entity-type=persona, age-range (same proof), guardian tags (if child), NO nullifier, NO merkle-root
| Data | On-chain (public) | Private (Merkle leaf only) |
|---|---|---|
| Entity type | Yes | — |
| Age range (e.g., "18+") | Yes (ZKP) | — |
| Merkle root | Yes (NP only) | — |
| Nullifier hash | Yes (NP only) | — |
| Guardian pubkey(s) | Yes (if child) | — |
| Full name | — | Yes |
| Nationality | — | Yes |
| Date of birth | — | Yes |
| Document type | — | Yes |
| Document number | — | Yes (Merkle leaf, for selective disclosure) |
| Document expiry | — | Yes (Merkle leaf, for consumer-side validity checks) |
| Photo hash | — | Yes (Merkle leaf, SHA-256 of verified photo) |
Credential expiry vs document expiry: The expires tag on the credential attestation (kind 31000 with type: credential) is the credential's validity period — when the credential itself stops being accepted. The documentExpiry Merkle leaf is the underlying document's expiry date — when the passport or licence expires. These are different: a credential might expire in 2 years but the passport doesn't expire for 10. Clients should check both.
All tiers carry age-range proofs when issued by a professional:
- Tier 1 (self-declared): No age-range proof (self-declaration is not verification)
- Tier 2 (peer-vouched): No age-range proof (peers cannot verify DOB)
- Tier 3 (professional, adult): Age-range proof "18+" from verified DOB
- Tier 4 (professional, adult+child): Age-range proof with specific range (e.g., "8-12", "13-17")
Age ranges for children:
| Age | Range tag | Tier | Scope |
|---|---|---|---|
| 0-3 | 0-3 |
4 | adult+child |
| 4-7 | 4-7 |
4 | adult+child |
| 8-12 | 8-12 |
4 | adult+child |
| 13-17 | 13-17 |
4 | adult+child |
| 18+ | 18+ |
3 | adult |
The DOB is NEVER published on-chain. Only the zero-knowledge age-range proof appears. The Pedersen range proof proves "this person's age falls within [min, max]" without revealing the exact age or DOB.
The Natural Person credential includes a merkle-root tag. The Merkle tree contains verified attributes as leaves:
Merkle Root
├── H("dateOfBirth:1990-05-15")
├── H("documentExpiry:2030-05-15")
├── H("documentNumber:123456789")
├── H("documentType:passport")
├── H("name:Alice Smith")
├── H("nationality:GB")
├── H("nullifier:<hash>")
└── H("photoHash:<sha256>")
Note: The leaves shown above are an example. The leaf set is variable per credential — different document types may include different attributes. The tree construction (RFC 6962 domain separation) and proof format are identical regardless of the number of leaves.
Selective disclosure: The subject can reveal any attribute by providing the leaf value and its Merkle proof (sibling hashes). The verifier (or any party) can verify the proof against the published Merkle root without seeing the other attributes.
Use cases for selective disclosure:
- Prove name to a bank without revealing nationality
- Prove nationality for a community policy without revealing name
- Prove document type for a regulatory check without revealing anything else
The entity-type tag is set at credential issuance and cannot be changed without a new credential:
| Entity type | Display | Can hold nullifier? | Can hold merkle-root? |
|---|---|---|---|
| natural_person | Person | Yes | Yes |
| persona | Alias | No | No |
Clients MUST display the entity type. A Persona MUST NOT be displayed as a verified person. The entity type is the primary signal for "is this a real identity or an alias?"
Nullifiers prevent duplicate identity creation. The nullifier is computed using length-prefixed encoding to prevent field-boundary collisions:
nullifier = SHA-256(
uint16be(len(document_type)) || document_type ||
uint16be(len(country_code)) || country_code ||
uint16be(len(document_number)) || document_number ||
uint16be(len(domain_tag)) || domain_tag
)
where domain_tag = "signet-nullifier-v2"
Each field is prefixed with its UTF-8 byte length as a 2-byte big-endian unsigned integer. This prevents ambiguity where field values containing delimiters could produce colliding nullifiers (e.g., docType="A||B" + country="C" must not collide with docType="A" + country="B||C").
Properties:
- Deterministic: Same document always produces the same nullifier
- One-way: Cannot recover document details from the nullifier
- Collision-resistant: Different documents produce different nullifiers; length-prefixed encoding prevents field-boundary ambiguity
- Cross-verifier consistent: Any verifier with the same document computes the same nullifier
When a verifier encounters a nullifier that already exists on a relay, this indicates either:
- The same person is getting re-verified (legitimate — supersede the old credential)
- Two different people presented the same document (fraud — flag for investigation)
When a subject presents multiple identity documents during a verification ceremony (e.g., passport AND driving licence AND national ID), the verifier SHOULD compute nullifiers for ALL documents, not just the primary. These form a nullifier family — a set of nullifiers that all belong to the same person.
nullifier_passport = SHA-256(LP("passport") || LP("GB") || LP("123456789") || LP("signet-nullifier-v2"))
nullifier_licence = SHA-256(LP("driving_licence") || LP("GB") || LP("SMITH901150J99XX") || LP("signet-nullifier-v2"))
nullifier_national_id = SHA-256(LP("national_id") || LP("GB") || LP("AB123456C") || LP("signet-nullifier-v2"))
where LP(s) = uint16be(len(s)) || s
The credential event includes:
["nullifier", "<primary_nullifier>"]— the primary nullifier (backwards compatible)["nullifier-family", "<nullifier>", "<document_type>"]— one tag per document in the family
Collision detection: When checking for duplicates, clients and relays MUST check ALL nullifiers in the incoming family against ALL nullifiers (both nullifier and nullifier-family tags) in existing credentials. A collision with ANY nullifier in ANY family indicates the same person has been verified before.
This significantly strengthens duplicate prevention: a person cannot circumvent the system by presenting a different document to a different verifier, because both documents' nullifiers are recorded and cross-checked.
| Weakness | Mitigation |
|---|---|
| Document shared between family members | Nullifier collision detected; professional investigates |
| Document number guessable (sequential) | Hash includes document type + country; brute-force requires knowing all three components |
| Multiple documents (passport + national ID) | Multi-document nullifier families (§20.8) — each document produces a nullifier, all are cross-checked |
| Country changes document format | Domain tag version ("signet-nullifier-v2") allows migration to new formula |
| Verifier collusion (issue without real document) | Anti-corruption framework (§7) applies; nullifier without document is detectable via anomaly patterns |
| eIDAS wallet credentials | When available, the eIDAS unique person identifier can serve as an additional nullifier source for EU citizens (§20.10) |
EU eIDAS 2.0 mandates unique person identifiers for ~450M EU citizens by December 2026. When a subject presents an eIDAS wallet credential during a Signet verification ceremony, the verifier MAY use the eIDAS unique person identifier as an additional nullifier source:
nullifier_eidas = SHA-256(LP("eidas") || LP(eidas_unique_id) || LP("signet-nullifier-v2"))
where LP(s) = uint16be(len(s)) || s
Note: The eIDAS nullifier uses only two data fields (type + identifier) since there is no country code or document number; the length-prefixed encoding ensures this cannot collide with document-based nullifiers which always have three data fields.
This provides a de facto perfect nullifier for EU citizens, as the eIDAS identifier is government-issued, unique, and machine-verifiable. The eIDAS nullifier is included in the nullifier family alongside document-based nullifiers.
When a subject presents the same document to a different verifier, the same nullifier is produced (because the nullifier is derived from the document details). The protocol distinguishes cross-verification from fraud by checking the subject's pubkey:
| Scenario | Nullifier | Subject pubkey | Interpretation |
|---|---|---|---|
| First verification | New | User's | New identity — record it |
| Cross-verification (same doc, new verifier) | Same | Same | Independent confirmation — higher IQ contribution |
| Document renewal (new number) | New | Same | New document — supersedes old credential |
| Document renewal (same number) | Same | Same | Re-verification — supersedes old credential |
| Fraud (someone else uses the document) | Same | Different | Duplicate detected — flag for investigation |
Cross-verification is the most valuable IQ signal because it represents independent professional confirmation of the same identity.
When a credential needs updating (name change, document renewal, tier upgrade), a new credential is issued with a ["supersedes", "<old_event_id>"] tag. The old credential receives a ["superseded-by", "<new_event_id>"] tag.
Credential v1 (original)
├── superseded-by: <v2_id>
└── [still on relay, but clients show as superseded]
Credential v2 (current)
├── supersedes: <v1_id>
└── [active, displayed by clients]
Rules:
- Only a professional verifier can issue a superseding credential
- The superseding credential MUST reference the old credential's event ID
- Clients MUST follow the chain and display only the current (non-superseded) credential
- The chain is append-only — superseded credentials cannot be un-superseded
When a subject's legal name changes (marriage, divorce, deed poll, court order):
- Subject presents new legal documents to a professional verifier
- Verifier builds new Merkle tree with updated name
- Verifier issues new Natural Person credential with
["supersedes", "<old_id>"] - Persona credential is UNAFFECTED (it carries no name)
Professional titles (PhD, Dr., KC/QC, Prof.) are NOT name changes. They are separate credentials issued by awarding bodies or verified by professionals, linked to the same pubkey. The Natural Person credential reflects the legal name only.
Foundational rule: child credentials MUST be sub-accounts of a verified parent or guardian.
The ceremony requires:
- Parent/guardian with Tier 3+ Signet credential (mandatory)
- Child's birth certificate or passport (DOB mandatory)
- Professional verifies parental authority (birth certificate name match, court order if applicable)
- Professional generates Pedersen range proof age-range proof from DOB
- Issues Natural Person + Persona credentials, both with age-range proof and
["guardian", "<parent_pubkey>"]tag - Child's real name NEVER published — only in private Merkle leaves
The ["guardian", "<parent_pubkey>"] tag is:
- Set by the professional verifier at issuance
- Immutable — cannot be changed without a new credential issued by a professional
- Multiple guardians supported (joint custody): multiple
["guardian", ...]tags - Present on BOTH Natural Person and Persona credentials
The transition from dependant to independent identity involves two separate decisions that may happen together or independently.
Decision 1 — Remove guardian authority (credential supersession):
- Subject visits a professional verifier with current identity documents
- Verifier issues new Tier 3 credential with
["age-range", "18+"]and["supersedes", "<child_credential_id>"] - New credential has NO guardian tag
- Persona credential also superseded with updated age-range and no guardian tag
- Guardian's delegation events revoked
Decision 2 — Sever key derivation (optional key rotation):
When the dependant's keys were derived from the guardian's mnemonic (e.g. via nsec-tree sub-identity derivation), the guardian can re-derive those keys indefinitely. Key rotation severs this ability:
- Subject generates a fresh, independently rooted mnemonic on their own device
- New mnemonic derives new keypairs (natural-person + persona)
- Old identity publishes a migration event (kind 31000,
type: migration) linking old pubkeys to new pubkeys, signed by the old keys - New credentials issued against new pubkeys with
["supersedes", "<old_credential_id>"] - Guardian's key server (e.g. Heartwood Pi) deletes the derived master secret
- Old keys become dead — credentials superseded, delegation revoked
Key rotation is optional. The subject may choose to retain the guardian's derivation capability as a recovery mechanism. Clients MUST clearly communicate the implications:
"Your guardian can still derive your private keys from their mnemonic. They could sign events as you. Choose this only if you trust them with recovery access."
| Guardian removed? | Keys rotated? | Result |
|---|---|---|
| Yes | Yes | Fully sovereign identity — complete independence |
| Yes | No | Independent but recoverable — guardian retains recovery capability as a safety net |
| No | Yes | Guardian retains legal authority but cannot derive keys — used when court restricts direct key access |
| No | No | Continuing dependant relationship (e.g. adult with permanent cognitive impairment) |
Migration event format:
{
"kind": 31000,
"pubkey": "<old-pubkey>",
"tags": [
["d", "migration:<old-pubkey>"],
["type", "migration"],
["p", "<new-pubkey>"],
["L", "nip-va"],
["l", "migration", "nip-va"]
],
"content": ""
}The migration event MUST be signed by the old key. Clients encountering a migration event SHOULD:
- Follow the
ptag to the new pubkey - Display credentials on the new pubkey as continuing the old identity's trust history
- Mark the old pubkey as migrated (not revoked — migration is a positive transition)
Guardianship may transfer due to foster care, adoption, bereavement, or court order. The protocol supports three transfer patterns:
Temporary delegation (guardian retains parental responsibility):
- Credential layer unchanged — guardian tag stays as original guardian
- Guardian delegates specific scopes to the temporary carer via kind 31000
type: delegation - Key derivation unchanged — original guardian retains derived keys
- Example: temporary foster placement, grandparent caring during hospital stay
Permanent transfer (parental responsibility transferred by court order):
- Professional issues new credential with new guardian tag (court order required as documentary evidence)
- Old guardian's delegation revoked
- Key rotation MUST occur — a person whose parental responsibility has been legally removed MUST NOT retain cryptographic access to the dependant's identity
- New keys are either independently rooted (professional generates fresh mnemonic for the dependant) or derived from the new guardian's mnemonic
- Nullifier chain links old and new identities via
["nullifier-chain", "<old_nullifier>"] - Old credentials superseded; old keys become dead
Bereavement:
- Court-appointed guardian added via superseding credential (existing §21.6 provisions)
- If deceased guardian's key server is inaccessible, key rotation occurs by necessity
- Nullifier chain preserves identity continuity
Duplicate identity resolution:
When key rotation creates a new identity, two pubkeys may temporarily claim the same person on relays. The nullifier system resolves this:
- Professional computes nullifier from the dependant's identity documents (same documents → same nullifier)
- Nullifier matches the old identity
- Old credential receives a revocation event from the professional
["nullifier-chain", "<old_nullifier>"]on the new credential links old and new- Clients follow the chain: old identity revoked → new identity active → same person confirmed
The old guardian cannot prevent this — revocation and new issuance are performed by a professional with legal authority (court order).
Delegation scopes control what a dependant can sign without guardian approval. The guardian adjusts scopes as the dependant matures:
| Stage | Signing behaviour | Guardian visibility |
|---|---|---|
| Full control | Guardian signs everything | N/A |
| Request/approve | Dependant requests, guardian approves via NIP-46 | Real-time approval prompts |
| Autonomous with alerts | Dependant signs autonomously | Real-time notifications |
| Autonomous with logging | Dependant signs autonomously | Audit log only |
| Full autonomy | No restrictions | No visibility (unless opted in) |
Scope transitions are enacted by the guardian updating the delegation event. The dependant's device requires no reconfiguration — the key server (Heartwood Pi or guardian's device) enforces the current scope on each signing request.
When a signing request falls outside the dependant's permitted scope, the key server SHOULD forward the request to the guardian's device for approval rather than rejecting outright. This allows the dependant to request exceptions without requiring a permanent scope change.
| Age range | Tier | Guardian required? | Typical capabilities |
|---|---|---|---|
| 0-7 | 4 | Yes (mandatory) | Guardian-managed account, no direct messaging |
| 8-12 | 4 | Yes (mandatory) | Limited messaging with guardian approval |
| 13-17 | 4 | Yes (mandatory) | More autonomy, guardian notification |
| 18+ | 3 | No | Full adult account |
Three distinct layers handle the complexity of real families:
Layer 1 — Credential level (immutable, set by professional): Guardian tags reflect legal parental responsibility. Only changeable via court order + new credential from a professional.
Layer 2 — Delegation level (flexible, set by guardian):
Guardians delegate specific permissions to step-parents, grandparents, teachers, or other trusted adults via kind 31000 delegation attestations (with type: delegation). Delegations are:
- Time-limited (expiry tag)
- Scope-limited:
full,activity-approval,content-management,contact-approval - Revocable by the guardian at any time
Layer 3 — Client level (app-specific, enforced locally): Applications enforce permissions based on Layer 1 and Layer 2 data: screen time limits, content filtering, activity approval workflows, contact restrictions.
When a document expires and is renewed (new passport number):
- New nullifier computed from new document details
["nullifier-chain", "<old_nullifier>"]tag links old and new- New credential supersedes old
- Continuity of identity maintained despite new document
Death: A professional issues a kind 31000 revocation attestation (with type: revocation) with reason "death." All credentials for the pubkey are considered revoked.
Key compromise: Subject visits a professional with identity documents. Professional revokes all old credentials and issues new ones on a new keypair. Existing vouches are lost (deliberate security measure — prevents an attacker who compromised the key from retaining social trust).
Incapacitation: Court-appointed guardian added via guardian tag on a superseding credential, issued by a professional with the court order.
Existing Nostr users can integrate with Signet without losing their established identity:
Path 1 — Sign existing keypair: The user's existing npub becomes their Natural Person identity. They visit a professional for verification. All followers, NIP-05, zaps, and reputation are preserved.
Path 2 — Existing npub becomes Persona:
The user creates a new keypair for their Natural Person identity and uses their existing npub as their Persona. An identity bridge attestation (kind 31000 with type: identity-bridge) links them with ring-signature privacy.
Both paths use existing mechanisms (NIP-05, identity bridges, credential chains). No automatic trust transfer between keypairs (prevents impersonation).
The primary defence against grooming is the combination of age-range proofs and in-person professional verification:
- Unverified accounts cannot enter child spaces. Communities require Tier 4 + age-range proof. An unverified account is rejected.
- Peer vouching cannot produce age-range proofs. Only professionals who see the person and their documents can issue age-range proofs. A Tier 2 vouch carries no age verification.
- Professionals see the actual person. AI can generate convincing documents, but a 40-year-old cannot present as a 14-year-old in person. Professionals verify documents daily as part of their existing practice.
- Guardian notifications. When a child's account receives contact from an unknown adult, the guardian is notified. Contact requires guardian approval in strict mode.
- "Signet me" challenges. Time-based word verification proves the current device holder matches the credential. Detects proxy use and reveals age gaps.
Preventing children from accessing adult-only spaces:
- Adult spaces require Tier 3+ with "18+" age-range proof. Self-declaration ("I am 18") is not sufficient.
- Nullifiers prevent second keypairs. A child cannot create an additional keypair without age-range proof — the deterministic nullifier from their document will match their existing child credential.
- Persona credentials carry age-range. The Persona issued during the two-credential ceremony MUST carry the same age-range proof as the Natural Person credential. A child's Persona is still identifiable as a child's Persona.
- Identity bridges inherit age-range. Any identity bridge created from a child credential carries the source credential's age-range constraint.
Clients implementing Signet MUST display:
| Element | Display |
|---|---|
| Unverified account | "Unverified" label, no trust indicators |
| Tier 4 child (0-12) | "Verified Child" + age range badge |
| Tier 4 teen (13-17) | "Verified Teen" + age range badge |
| Tier 3 adult (18+) | "Verified Adult" badge |
| Guardian link | "Guardian: [name/pubkey]" on child profiles |
| Age-gap contact | Warning to child + guardian notification |
| Missing age proof | "Age not verified" warning |
Guardian accounts (pubkeys listed in a child's guardian tags) have access to:
- DM policy: Block all / approve-only / notify / allow
- Content filtering: Strict / moderate / off
- Contact restrictions: Whitelist / blacklist / open with notifications
- Meeting detection: "Signet me" required for first real-world contact
- Delegation: Grant specific permissions to trusted adults (step-parent, grandparent, teacher)
These controls are enforced at the client level (Layer 3 of the family structure model, §21.4.5).
Communities can adopt pre-defined safety policies:
| Template | Min tier | Age range | Guardian required? | DM policy |
|---|---|---|---|---|
| Child-safe (under-13) | 4 | 0-12 | Yes | Guardian-approved only |
| Teen (13-17) | 4 | 13-17 | Yes (notify) | Open with logging |
| Adult-only (18+) | 3 | 18+ | No | Open |
| Mixed-age | 3 (adults) / 4 (children) | All | Per age group | Age-appropriate |
The four-tier system ensures that lack of documents does not mean exclusion:
- Tier 1 (self-declared): Anyone can create an account. No documents required. Zero barrier to entry.
- Tier 2 (peer-vouched): Real-world connections can vouch. No professional or document needed.
- Tier 3 (professional, adult): Requires documents and professional verification.
- Tier 4 (professional, adult+child): Requires documents, professional, and guardian.
Every person starts at Tier 1. The system provides degrees of confidence, not binary access.
Professional verifiers can accept a range of identity documents beyond passports and national IDs:
- UNHCR refugee travel documents
- Stateless person travel documents (1954 Convention)
- Temporary residence permits
- Birth certificates (for children)
- Court-issued identity documents
- Religious community attestations (with professional co-signing)
- Employer attestations (for jurisdiction-specific contexts)
The verifier's professional judgement determines which documents are sufficient. The credential records the document type used, allowing communities to set their own acceptance thresholds.
For people with no documents at all (homeless, displaced, stateless):
- Tier 1 — immediate access. Create a keypair, start participating.
- Tier 2 — community vouching. Local people who know the person can vouch.
- Support pathway — NGOs, shelters, legal aid organisations can help obtain documents over time
- Tier 3 — when documents are eventually obtained, professional verification upgrades the credential
The system never requires documents as a prerequisite for basic participation. Documents unlock higher tiers, which unlock access to communities with stricter policies — but Tier 1 and Tier 2 are always available.
Communities choose their own verification thresholds. A community for casual conversation might accept Tier 1. A community for financial advice might require Tier 3. A community for children requires Tier 4.
This is not gatekeeping — it is informed choice. Each community publishes its policy (kind 30078). Users can see what is required before joining. No central authority decides who can participate where.
The goal is that every person, regardless of documentation status, has a path to meaningful participation — while communities retain the right to set appropriate safety standards for their members.
Not all jurisdictions provide equal assurance for professional verification. A credential issued by a verifier in a jurisdiction with strong professional regulation, public registries, and digital credential infrastructure carries more weight than one from a jurisdiction with weaker oversight.
The jurisdiction confidence score (0-100) is computed from:
| Factor | Max Points | Description |
|---|---|---|
| Corruption Perception Index | 20 | Transparency International CPI (updated annually, publicly available). Score = CPI / 5, capped at 20. A jurisdiction with CPI 90 (Denmark) scores 18. A jurisdiction with CPI 20 scores 4. |
| Professional body coverage | 15 | Number of regulated professions (1pt per body, capped at 15) |
| Public register availability | 15 | Proportion of bodies with queryable public registers |
| Digital credential issuance | 15 | Proportion of bodies issuing machine-verifiable credentials |
| Data protection maturity | 15 | Explicit consent, erasure rights, portability, breach notification, cross-border restrictions |
| Mutual recognition | 10 | Number of mutual recognition partners (1pt per partner, capped at 10) |
| E-signature recognition | 5 | Whether electronic signatures are legally recognised |
| Legal system compatibility | 5 | Common-law and civil-law score highest (well-established professional regulation) |
The CPI factor is the single most important signal. In jurisdictions where bribery is common, the confidence that a professional genuinely verified documents (rather than rubber-stamping for a fee) is lower. This is not discrimination — it is statistical confidence based on publicly available data.
Clients MAY use jurisdiction confidence scores to:
- Weight Signet Score contributions from different jurisdictions (a Tier 3 credential from a high-confidence jurisdiction contributes more to the Signet Score)
- Display jurisdiction confidence alongside credentials
- Set minimum jurisdiction confidence in community policies
Clients MUST NOT use jurisdiction confidence to deny Tier 1 or Tier 2 credentials, which are jurisdiction-independent.
| Mode | Example | Solution |
|---|---|---|
| Captured/corrupt bodies | State-controlled professional bodies | Cross-jurisdiction verification: subject verified by professional in a free-body jurisdiction |
| Nonexistent bodies | No formal professional regulation | Embassy model: international NGOs and law firms with globally-licensed professionals serve as remote trust anchors |
| Non-digital bodies | Paper-based registries, no APIs | Manual registry cross-checks (weighted lower); eIDAS 2.0 mandates machine-readable interfaces by December 2026 |
For jurisdictions where Tier 3/4 is not achievable, Tier 1 + Tier 2 (self-declared + peer vouches) are always available. "Some trust" is infinitely better than "no trust."
In jurisdictions with high corruption, bribery in document issuance is currently invisible — cash changes hands, fake documents appear, nobody knows. Signet inverts this: every credential is public, permanently traceable to a specific verifier, and subject to anomaly detection.
A corrupt official who rubber-stamps verifications creates a permanent, public audit trail:
- Volume anomalies — issuing 200 verifications/week when the norm is 5 (§7 Layer 3)
- Geographic impossibilities — verifying people in locations they couldn't plausibly reach
- Nullifier collisions — multiple people presenting the same document
- Cross-verification failures — credentials that don't survive independent confirmation by a second verifier
The corruption that was invisible becomes the evidence that catches itself. The more a corrupt official does it, the more obvious the pattern. This has the potential to reduce bribery in document issuance — not by preventing it, but by making it permanently traceable.
Cross-jurisdiction verification (§24.4) provides the escape valve: a person in a corrupt jurisdiction can get verified by a professional in a less corrupt one. The credential from the independent jurisdiction carries higher confidence, and the discrepancy (if any) between the two credentials surfaces the problem.
Currently, bribery is a Nash equilibrium in many developing countries — everyone does it because the cost of not doing it (waiting months, being denied) exceeds the cost of doing it (a small fee, no consequences). Signet breaks the equilibrium from both sides:
- The corrupt official carries every bribed verification as a permanent liability. One anomaly detection flag and their entire history unravels — taking down every credential they ever signed. Their career, their professional registration, and their reputation are on the line forever.
- The person paying the bribe has their entire identity infrastructure linked to that official. If the official falls, their credential's IQ contribution drops to near zero. Everything built on top — service access, family trust chains, children's verifications — crumbles. The bribe isn't a one-time transaction; it's a permanent bond to a ticking time bomb.
The honest route becomes the rational economic choice, even in a corrupt environment. A slow, honest credential lasts forever. A fast, corrupt credential is a house of cards. Both sides of the bribe are incentivised to go straight — not through enforcement, but through structural incentives. The system doesn't need to catch everyone — it just needs to shift the equilibrium.
The UN estimates $1 trillion is paid in bribes annually and $2.6 trillion stolen through corruption — roughly 5% of global GDP (~$5.75 trillion in 2025). Even reducing document-related bribery by a fraction of a percent moves billions. Anti-corruption as a structural byproduct of transparent identity verification, at zero additional cost.
Sources: UN Secretary-General, Security Council (2018), World Economic Forum (2018), World Bank.
The Merkle tree leaf keys and the document type strings used in nullifier computation are defined by the Signet Document Registry, which is maintained separately from this specification.
The registry lists, per country:
- Available document types (passport, national ID, driving licence, etc.)
- Required and optional fields for each document type
- Which fields contribute to the nullifier computation
- Whether the document number changes on renewal
- Country-specific attributes (e.g.,
gb:nationalInsurance,in:aadhaar,us:ssn)
The registry is an open-source community resource hosted alongside the protocol. Adding a new country or document type is a registry update — it does not require a protocol revision. The protocol is document-agnostic by design: it defines the Merkle tree format and nullifier computation, not the contents.
Protocol complexity should not prevent partial adoption. Signet defines three progressive implementation levels so that client developers can integrate incrementally.
Effort: A weekend. Event kinds: 31000 (type: credential), 31000 (type: vouch).
Read credentials and vouches for a pubkey, compute a basic trust tier and score, display a badge. No new cryptography beyond Schnorr signature verification (which Nostr clients already implement). This is the NIP-05 equivalent — minimal effort, immediate value.
The reference implementation provides src/badge.ts with computeBadge(), buildBadgeFilters(), and related helpers.
Effort: A few days. Event kinds: 31000 (credential, vouch), 30078 (policy).
Level 1 + users can vouch for each other (create kind 31000 vouch attestations) and communities can set policies (kind 30078). This is the viral layer — peer vouching creates Tier 2 network effects without requiring professional verifiers.
Effort: Weeks to months. Event kinds: 31000 (all attestation types), 30078 (policy), 30482-30484 (voting).
All event kinds, full cryptographic stack: Merkle trees for selective disclosure, Pedersen range proofs for age range proofs, ring signatures for issuer privacy, two-credential ceremony, nullifier computation, guardian delegation, anomaly detection, and the voting extension.
The recommended adoption path is:
- Get Level 1 into 10+ Nostr clients (badges create visibility and demand)
- Enable Level 2 for viral peer vouching
- Level 3 for clients that want full verification capability
This section documents known privacy trade-offs in the Signet protocol design, the mitigations available to users and implementers, and the accepted trade-offs where transparency or continuity was deliberately chosen over maximum privacy.
Credential chains (supersedes/superseded-by tags) create a publicly observable timeline of credential lifecycle events. An observer can follow the full history of a Natural Person's credential renewals, name changes, and tier upgrades through these links.
Mitigation options for privacy-sensitive users:
- Issue new credentials without
supersedestags, sacrificing continuity proof for privacy - Use a fresh keypair when replacing credentials (requires re-establishing trust)
- Clients SHOULD warn users that supersedes chains are publicly visible
Accepted trade-off: Credential chains provide important auditability and credential lifecycle management. For most users, the transparency is a feature (proving continuous identity). Users who need stronger privacy should avoid credential chains.
Guardian delegation tags (["guardian", "<parent_pubkey>"]) on child Persona credentials publicly link the child's anonymous account to their parent's verified Natural Person identity. This reveals:
- That the account belongs to a child
- Which specific adult is their guardian
- The child's age range
Mitigation (future): Guardian tags on Persona credentials SHOULD reference the parent's Persona pubkey rather than their Natural Person pubkey. This preserves the guardian relationship while maintaining the parent's anonymity. Implementations MUST validate that the referenced guardian holds a valid credential regardless of which pubkey is used.
Current status: Implementations currently use the Natural Person pubkey for simplicity. A migration path will be defined in a future spec revision.
Identity bridge attestations (kind 31000 with type: identity-bridge) embed the full ring of public keys used for the ring signature. If a Persona issues multiple identity bridges over time with different randomly selected decoy members, an observer can intersect the ring sets to identify the common member — the actual signer.
With a ring of size n and k independent bridge events, the expected intersection size is approximately 1 + (n-1) * (1/pool_size)^(k-1), which rapidly approaches 1 (the signer) as k increases.
Mitigations:
- Identity bridges SHOULD be issued at most once per Persona. Re-issuance MUST reuse the same ring members
- Larger rings (50+ members) require more samples for intersection attacks to succeed
- Clients MUST NOT automatically refresh identity bridges — manual re-issuance only, with a warning about ring intersection risks
- Future: consider using zero-knowledge proofs of set membership instead of explicit rings
Cold-call verification solves a common trust problem: a customer receives a call claiming to be from their bank, law firm, or other institution. How can the customer verify the caller is genuine, without installing a new app, without sharing personal data, and without depending on a central authority?
Signet provides a mechanism for institutions to publish their verification pubkeys via .well-known/signet.json, and for both parties to independently derive the same spoken words from an ephemeral ECDH shared secret.
Institutions publish a JSON document at https://<domain>/.well-known/signet.json:
{
"version": 1,
"name": "Acme Legal LLP",
"pubkeys": [
{
"id": "key-2026-01",
"pubkey": "<64-char hex secp256k1 x-only pubkey>",
"label": "Primary Verification Key",
"created": "2026-01-01T00:00:00Z"
}
],
"relay": "wss://relay.example.com",
"policy": {
"rotation": "annual",
"contact": "[email protected]"
}
}Validation rules for clients fetching this document:
- MUST use HTTPS — HTTP is rejected.
- Response body MUST NOT exceed 10,240 bytes (10 KB) for version 1 documents, or 102,400 bytes (100 KB) for version 2 documents (see §17.7.1 for the extended schema).
versionMUST be1or2. Version 2 documents may includeentity,registry, andstafffields as defined in §17.7.1.nameMUST be a non-empty string.pubkeysMUST be a non-empty array with at most 20 entries.- Each
pubkeyvalue MUST be a 64-character lowercase hexadecimal string (x-only secp256k1). - Clients MAY cache the response for up to 24 hours.
To let the customer and institution find each other (e.g. when the customer initiates the check in an app and reads a code over the phone), a human-friendly session code is derived from the ephemeral pubkey:
NATOWORD-NNNN
Examples: BRAVO-7742, NOVEMBER-0053, XRAY-1991
Derivation:
- Compute
hash = SHA-256(ephemeralPubkey_bytes). natoIndex = hash[0] mod 26→ select from the NATO phonetic alphabet.digits = ((hash[1] << 24) | (hash[2] << 16) | (hash[3] << 8) | hash[4]) mod 10000→ zero-padded to 4 digits.- Code =
NATO[natoIndex] + "-" + digits.padStart(4, "0").
The session code is a human-readable fingerprint of the ephemeral pubkey, not a secret. It allows the institution to look up the associated ephemeral pubkey from a relay (Phase 2 feature) without the customer needing to read out 64 hex characters.
NATO phonetic alphabet (Signet uses the ICAO standard):
ALFA BRAVO CHARLIE DELTA ECHO FOXTROT GOLF HOTEL INDIA JULIET KILO LIMA MIKE NOVEMBER OSCAR PAPA QUEBEC ROMEO SIERRA TANGO UNIFORM VICTOR WHISKEY XRAY YANKEE ZULU
The verification flow uses a one-time ECDH exchange to derive a shared secret that neither party possessed before the call:
Customer side (initiate):
- Fetch
https://<institution-domain>/.well-known/signet.jsonand select a pubkey. - Generate an ephemeral secp256k1 keypair
(ephPriv, ephPub). - Compute
sharedPoint = secp256k1.ECDH(ephPriv, institutionPubkey). - Derive
sharedSecret = SHA-256(x-coordinate of sharedPoint)(32 bytes). - Derive words:
words = SPOKEN-DERIVE(sharedSecret, "signet:cold-call", currentEpoch, wordCount=3). - Generate
sessionCode = NATO-WORD + "-" + 4-digitsfrom ephemeral pubkey. - Display
wordson screen — the customer expects to hear these words from the caller. - Share
ephemeralPubkey(orsessionCode) with the institution (read it out, or relay lookup). - Zero the ephemeral private key immediately after use.
Institution side (complete):
- Receive the customer's
ephemeralPubkey(via relay lookup by session code, or spoken by customer). - Compute
sharedPoint = secp256k1.ECDH(institutionPrivkey, ephemeralPubkey). - Derive
sharedSecret = SHA-256(x-coordinate of sharedPoint). - Derive
words = SPOKEN-DERIVE(sharedSecret, "signet:cold-call", currentEpoch, wordCount=3). - Read the words out to the customer.
If the words match, the caller holds the private key corresponding to a pubkey published in the institution's .well-known/signet.json at the time the customer fetched it.
Cold-call words use the same SPOKEN-DERIVE function as "Signet me" (§15), with a different context string for domain separation:
- Algorithm:
HMAC-SHA256(sharedSecret, utf8("signet:cold-call") || counter_be32) - Context:
"signet:cold-call"(distinct from"signet:verify"used by "Signet me") - Counter:
Math.floor(unixSeconds / 30)— 30-second epoch, same as "Signet me" - Tolerance: ±1 epoch (accounts for up to 30 seconds of clock skew between parties)
- Default word count: 3
- Wordlist: spoken-clarity wordlist (same as "Signet me")
The context separation ensures that cold-call words and peer-verification words are always different, even if the same shared secret were somehow reused.
What cold-call verification provides:
- Institution authenticity: The caller knows the institution's private key — they are who they claim to be (or the key has been compromised).
- Freshness: The ephemeral keypair is generated per-call. Replaying an old session code produces different words in the next epoch.
- No data disclosure: The customer's personal data is never transmitted. The ECDH produces a shared secret without either party revealing their private key.
- No central authority: Verification relies only on DNS (to reach the
.well-knownendpoint) and the HTTPS PKI.
Limitations:
- DNS/TLS trust: If the institution's domain is compromised, an attacker could substitute their own pubkeys in
.well-known/signet.json. Clients SHOULD cache the pubkeys and warn if they change unexpectedly. - Key compromise: If the institution's private key is leaked, an attacker can impersonate the institution. Institutions SHOULD rotate keys annually and support multiple simultaneous pubkeys for transition periods.
- Epoch synchronisation: Both parties must be within ±30 seconds. Network time attacks could desynchronise them, but ±1 epoch tolerance mitigates minor skew.
- No relay integration yet (Phase 1): In the current implementation, the customer must share the
ephemeralPubkeyorsessionCodeverbally. Phase 2 will add relay-based session code resolution so the institution can look up the ephemeral pubkey automatically.
Not yet implemented. Described here for future implementers.
The institution publishes a relay URL in .well-known/signet.json (relay field). The customer's app publishes the ephemeral pubkey to this relay, tagged with the session code. The institution's system subscribes and automatically retrieves the ephemeral pubkey without requiring the customer to read it out.
This eliminates the need for the customer to verbally communicate anything except confirming that the words match.
This specification is a living document. It will evolve through community feedback and implementation experience.
{ "kind": 31000, "pubkey": "<subject_pubkey>", "tags": [ ["d", "credential:<subject_pubkey>"], // direct claim (self-issued) ["p", "<subject_pubkey>"], ["type", "credential"], ["tier", "1"], ["verification-type", "self"], ["scope", "adult"], ["method", "self-declaration"], ["algo", "secp256k1"], ["L", "signet"] ], "content": "" }