signet-protocol 1.11.0 → 1.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -10
- package/llms-full.txt +3744 -0
- package/llms.txt +134 -0
- package/package.json +8 -3
package/llms.txt
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# Signet Protocol
|
|
2
|
+
|
|
3
|
+
> Nostr has no identity layer: anyone can impersonate anyone, and there's no way to verify age for child safety. Signet is a decentralised identity verification protocol for Nostr that lets users prove claims about their identity (age, profession, parenthood) using zero-knowledge proofs, without revealing personal data or relying on a central authority.
|
|
4
|
+
|
|
5
|
+
Signet is for Nostr client developers, relay operators, and anyone building identity-aware applications on Nostr. It provides a TypeScript library (`signet-protocol`) that handles credentials, vouches, trust scoring, and ZKP age proofs. Unlike centralised identity services, Signet anchors trust to professional bodies (law societies, medical boards, notary commissions) and uses cryptographic proofs instead of data collection.
|
|
6
|
+
|
|
7
|
+
## Getting Started
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install signet-protocol
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Requires Node ≥22. ESM only (no CommonJS `require` export).
|
|
14
|
+
|
|
15
|
+
Display a verification badge for any Nostr pubkey (Level 1 integration: a weekend):
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
import { computeBadge, buildBadgeFilters, fetchFromRelay, meetsMinimumTier } from 'signet-protocol';
|
|
19
|
+
|
|
20
|
+
// Build relay filters and fetch events (fetchFromRelay is the package's own minimal relay client)
|
|
21
|
+
const filters = buildBadgeFilters(['<hex-pubkey>']);
|
|
22
|
+
const events = await fetchFromRelay('wss://relay.example.com', filters);
|
|
23
|
+
|
|
24
|
+
// Compute badge (async: returns Promise<BadgeInfo>)
|
|
25
|
+
const badge = await computeBadge('<hex-pubkey>', events, { verifySignatures: true });
|
|
26
|
+
// badge.tier => 3, badge.score => 106, badge.displayLabel => "Verified (Tier 3)"
|
|
27
|
+
|
|
28
|
+
// Gate features on verification level
|
|
29
|
+
if (meetsMinimumTier(badge, 2)) {
|
|
30
|
+
// allow posting in verified-only community
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
`fetchFromRelay(relayUrl: string, filters: NostrFilter[]): Promise<NostrEvent[]>` connects, collects events until EOSE and disconnects. Non-localhost relay URLs must be `wss://`. Any other Nostr client works equally well: pass the `buildBadgeFilters` output as REQ filters and give the returned events to `computeBadge`.
|
|
35
|
+
|
|
36
|
+
## Key Concepts
|
|
37
|
+
|
|
38
|
+
- **4 verification tiers**: Self-declared (Tier 1) → Web-of-trust via peer vouches (Tier 2) → Professional verification with ZKP age proof (Tier 3) → Professional + child safety (Tier 4)
|
|
39
|
+
- **Signet IQ** (0–200): A continuous identity quality score measuring statistical confidence that an identity is real, not fabricated. Weighted by professional verification, in-person vouches, online vouches, account age, and identity bridges. 100 represents the current government standard. IQ is not reputation: it measures identity quality, not trustworthiness. The source calls the same number the "Signet Score" (`TrustScoreBreakdown.score`).
|
|
40
|
+
- **Zero-knowledge age proofs**: Pedersen range proofs on secp256k1 (the `@forgesworn/range-proof` dependency; no trusted setup) prove age ranges (e.g. "adult" or "child aged 8–12") without revealing date of birth.
|
|
41
|
+
- **Document nullifiers**: SHA-256 of document fields prevents duplicate identity registration without storing any personal data.
|
|
42
|
+
- **Single attestation kind** (31000, NIP-VA) plus policies (30078): All identity attestations (credentials, vouches, verifier management, challenges, revocations, identity bridges, delegation) share kind 31000, differentiated by `type` tag. The voting extension (kinds 30482–30484) is a separate package, `nostr-anon-vote`.
|
|
43
|
+
|
|
44
|
+
## API Surface
|
|
45
|
+
|
|
46
|
+
Conventions used throughout:
|
|
47
|
+
- Private keys are 64-character hex strings (not `Uint8Array`). Public keys are 64-character x-only hex. The identity functions are the exception: `createSignetIdentity` takes a BIP-39 mnemonic and `createSignetIdentityFromNsec` takes a bech32 nsec string or 32 raw bytes.
|
|
48
|
+
- Of the `create*` functions documented in this file, the event-signing ones (credentials, vouches, policies, verifiers, challenges, revocations, identity bridges, guardian delegations) are async and return a signed `Promise<NostrEvent>` (with `id` and `sig`). The matching `build*Event` functions return an unsigned `UnsignedEvent`. The documented `create*` helpers that are not event signers return what is stated beside them: `createAgeRangeProof` (synchronous, `RangeProof`), `createSignetIdentity` / `createSignetIdentityFromNsec` (synchronous, `SignetIdentity`) and `createLinkageProof` (synchronous, `LinkageProof`). The package root exports other `create*` functions that this file does not cover (for example `createTwoCredentialCeremony`, `createBondProof`); check their return types in `src/` rather than assuming the event-signer shape.
|
|
49
|
+
- Tiers are `SignetTier` (`1 | 2 | 3 | 4`). Timestamps (`expiresAt`, `occurredAt`, `accountCreatedAt`) are unix seconds.
|
|
50
|
+
- Throws use the `SignetError` hierarchy (`SignetValidationError`, `SignetCryptoError`).
|
|
51
|
+
|
|
52
|
+
### Credentials (kind 31000, type: credential)
|
|
53
|
+
- `createSelfDeclaredCredential(privateKey, scope = 'adult', expiresAt?)`: Tier 1. `scope` is `'adult' | 'adult+child'`.
|
|
54
|
+
- `createPeerVouchedCredential(issuerPrivateKey, subjectPubkey, opts)`: Tier 2. `opts: { assertionEventId: string; assertionRelay?: string; expiresAt?: number }`. `assertionEventId` is the subject's Tier 1 event.
|
|
55
|
+
- `createProfessionalCredential(verifierPrivateKey, subjectPubkey, opts)`: Tier 3. `opts: { assertionEventId: string; profession: string; jurisdiction: string; actualAge: number; assertionRelay?: string; expiresAt?: number; occurredAt?: number; proofBlob?: string }`. Generates an `'18+'` range proof from `actualAge` into the event content.
|
|
56
|
+
- `createChildSafetyCredential(verifierPrivateKey, subjectPubkey, opts)`: Tier 4. Same `opts` as Tier 3 plus `ageRange: string` (the range proved, e.g. `'8-12'`).
|
|
57
|
+
- `verifyCredential(event)`: `Promise<{ signatureValid: boolean; structureValid: boolean; expired: boolean; errors: string[] }>`
|
|
58
|
+
- `parseCredential(event)`: `ParsedCredential | null`
|
|
59
|
+
|
|
60
|
+
`ageRange` format everywhere: `'<min>-<max>'` inclusive (e.g. `'8-12'`, `'13-17'`) or `'<min>+'` (e.g. `'18+'`, upper bound 150). Anything else fails to parse.
|
|
61
|
+
|
|
62
|
+
Default credential expiry is two years (`DEFAULT_CREDENTIAL_EXPIRY_SECONDS`) when `expiresAt` is omitted.
|
|
63
|
+
|
|
64
|
+
### Vouches (kind 31000, type: vouch)
|
|
65
|
+
- `createVouch(voucherPrivateKey, params)`: vouch for another user. `params: { subjectPubkey: string; method: 'in-person' | 'online'; voucherTier: SignetTier; voucherScore: number; context?: string; occurredAt?: number }`. `voucherScore` is the voucher's own Signet IQ (0–200).
|
|
66
|
+
- `hasEnoughVouches(vouches, subjectPubkey, threshold = 3, minVoucherTier = 2)`: `boolean`. `true` when the number of qualifying vouches (one per voucher, voucher tier ≥ `minVoucherTier`) reaches `threshold`. `countQualifyingVouches(vouches, subjectPubkey, minVoucherTier?)` returns the count.
|
|
67
|
+
|
|
68
|
+
### Trust & Badges
|
|
69
|
+
- `computeBadge(pubkey, events, opts?)`: `Promise<BadgeInfo>`. `opts: { verifySignatures?: boolean (default false); now?: number }`. `BadgeInfo: { pubkey, tier, tierLabel, score, isVerified, displayLabel, credentialCount, vouchCount, entityType? }`.
|
|
70
|
+
- `buildBadgeFilters(pubkeys: string[])`: `Array<{ kinds: number[]; '#d': string[] }>`. One filter for kind 31000 with d-tags `credential:<pubkey>` and `vouch:<pubkey>` for each pubkey.
|
|
71
|
+
- `meetsMinimumTier(badge: BadgeInfo, minTier: SignetTier)`: `boolean`. `true` only when `badge.isVerified` and `badge.tier >= minTier`.
|
|
72
|
+
- `computeTrustScore(subjectPubkey, credentials, vouches, accountCreatedAt?, bridges?)`: `TrustScoreBreakdown` (`{ score, tier, professionalVerifications, inPersonVouches, onlineVouches, accountAgeDays, signals }`). This is the full Signet IQ. It does not verify signatures.
|
|
73
|
+
|
|
74
|
+
`badge.score` is on the same 0–200 scale and uses the same weights as `computeTrustScore(...).score` (professional verification 80, in-person vouch up to 16, online vouch up to 4, each vouch scaled by the voucher's score ÷ 200), but it is a Level 1 approximation, not the full IQ: `computeBadge` ignores account age and identity bridges and does not apply the per-type caps (1 professional, 3 in-person vouches, 5 online vouches). Use `computeTrustScore` when you need the full IQ.
|
|
75
|
+
|
|
76
|
+
### Policies (kind 30078, NIP-78)
|
|
77
|
+
- `createPolicy(operatorPrivateKey, params)`: community verification requirements. `params: { communityId: string; adultMinTier: SignetTier; childMinTier: SignetTier; enforcement: 'client' | 'relay' | 'both'; minScore?: number; modMinTier?: SignetTier; verifierBond?: number; revocationThreshold?: number; description?: string }`. The d-tag is `signet:policy:<communityId>`.
|
|
78
|
+
- `new PolicyChecker(policyEvent: NostrEvent)`: throws `SignetValidationError('Invalid policy event')` if the event is not a valid policy. Methods: `getPolicy(): ParsedPolicy`, `checkAdult(tier, score)`, `checkChild(tier, score)`, `checkModerator(tier, score)`. Each check returns `PolicyCheckResult`: `{ allowed: boolean; reason?: string; requiredTier: SignetTier; actualTier: SignetTier; requiredScore?: number; actualScore?: number }`.
|
|
79
|
+
- `parsePolicy(event)`: `ParsedPolicy | null`. `checkPolicyCompliance(policy, userTier, userScore, { isChild?, isModerator? })`: `PolicyCheckResult`.
|
|
80
|
+
|
|
81
|
+
### Verifiers (kind 31000, type: verifier)
|
|
82
|
+
- `createVerifierCredential(privateKey, params)`: register as professional verifier. `params: { profession: string; jurisdiction: string; licenceHash: string; professionalBody: string; statement?: string; bondProof?: BondProof }`.
|
|
83
|
+
- `checkCrossVerification(verifierPubkey, vouches, verifierCredentials)`: activation check. Returns `{ activated: boolean; vouchCount: number; professions: string[]; errors: string[] }`.
|
|
84
|
+
- `isVerifierRevoked(verifierPubkey, revocations)`: `boolean`
|
|
85
|
+
|
|
86
|
+
### Challenges and Revocations (kind 31000, type: challenge / revocation)
|
|
87
|
+
- `createChallenge(reporterPrivateKey, params)`: `Promise<NostrEvent>`. `params: { verifierPubkey: string; reason: 'anomalous-volume' | 'registry-mismatch' | 'fraudulent-attestation' | 'licence-revoked' | 'other'; evidenceType: string; reporterTier: SignetTier; evidence: string; occurredAt?: number }`. `evidence` becomes the event content.
|
|
88
|
+
- `createRevocation(authorityPrivateKey, params)`: `Promise<NostrEvent>`. `params: { verifierPubkey: string; challengeEventId: string; confirmations: number; bondAction: 'slashed' | 'returned' | 'held'; scope: 'full' | 'partial'; effectiveAt: number; summary: string }`.
|
|
89
|
+
- `countChallengeConfirmations(challengeEventId, confirmationEvents, credentialEvents)`: `number`
|
|
90
|
+
- `hasReachedRevocationThreshold(confirmations, threshold = 5)`: `boolean`
|
|
91
|
+
- `parseChallenge(event)` / `parseRevocation(event)`: parsed object or `null`.
|
|
92
|
+
|
|
93
|
+
### Identity Bridge (kind 31000, type: identity-bridge)
|
|
94
|
+
- `createIdentityBridge(anonPrivateKey, realPrivateKey, ring, signerIndex, ringMinTier, opts?)`: link anonymous account to verified identity via ring signature. `ring: string[]` of pubkeys (at least 5, `MIN_BRIDGE_RING_SIZE`); `opts: { occurredAt?: number }`. Signed by the anonymous key.
|
|
95
|
+
- `selectDecoyRing(verifiedPubkeys, realPubkey, ringSize = 5)`: `{ ring: string[]; signerIndex: number }`
|
|
96
|
+
- `verifyIdentityBridge(event, opts?)`: `Promise<boolean>`. `opts: { maxAgeSeconds?: number }` (default 24 hours; `0` disables the freshness check).
|
|
97
|
+
|
|
98
|
+
### ZKP Age Proofs
|
|
99
|
+
- `createAgeRangeProof(age: number, ageRange: string, subjectPubkey?: string)`: `RangeProof` (synchronous). `subjectPubkey` binds the proof to one credential.
|
|
100
|
+
- `verifyAgeRangeProof(proof, expectedAgeRange, expectedSubjectPubkey?)`: `boolean`. Verify against the age policy you expect, not the range the proof claims.
|
|
101
|
+
- `serializeRangeProof(proof): string` / `deserializeRangeProof(json: string): RangeProof`
|
|
102
|
+
|
|
103
|
+
### Identity & Personas
|
|
104
|
+
- `createSignetIdentity(mnemonic, passphrase?)` / `createSignetIdentityFromNsec(nsec: string | Uint8Array)`: derive a master identity. Both return `SignetIdentity` (`{ root, naturalPerson, persona, mnemonic? }`) with natural-person and persona sub-identities.
|
|
105
|
+
- `deriveAdditionalPersona(root, name, index = 0)`: further `Persona`. `deriveDependantIdentity(root, dependantIndex)`: `DependantIdentity` (`{ derivationPath, naturalPerson, persona }`) for a child.
|
|
106
|
+
- `createLinkageProof(root, child, type: 'blind' | 'full')` / `verifyLinkageProof(proof)` (returns `boolean`): prove or verify that a child identity belongs to a root.
|
|
107
|
+
- `destroyIdentity(identity)`: zeroes the root and persona keys.
|
|
108
|
+
- nsec-tree's own `fromMnemonic` / `fromNsec` are not re-exported; this package wraps them as the two `createSignetIdentity*` functions. `derive` and `zeroise` are re-exported.
|
|
109
|
+
|
|
110
|
+
### Guardian Delegation
|
|
111
|
+
- `createGuardianDelegation(guardianPrivateKey, params)`: kind 31000 (type: delegation) event scoping a guardian's authority over a dependant. `params: { childPubkey: string; delegatePubkey: string; scope: 'full' | 'activity-approval' | 'content-management' | 'contact-approval'; expiresAt?: number; occurredAt?: number }`.
|
|
112
|
+
|
|
113
|
+
### signet-protocol/experimental (unstable)
|
|
114
|
+
|
|
115
|
+
Import from `signet-protocol/experimental`. Nothing here is exported from the package root. These are unreleased draft contracts under protocol review: any export may change or be removed in any release, including a minor or patch, with no deprecation period. Do not depend on them for production data.
|
|
116
|
+
|
|
117
|
+
- Private vault keys (see `docs/private-vault-v1.md`): `vaultPurpose`, `parseVaultPurpose`, `vaultKeyContext`, `deriveVaultIdentity`, `vaultIdentityFromMnemonic`; type `VaultDataset`.
|
|
118
|
+
- Private vault checkpoints: `VAULT_EVENT_KIND`, `MAX_VAULT_CHUNKS`, `MAX_VAULT_DEVICES`, `MAX_VAULT_CONTROL_BYTES`, `MAX_VAULT_CHUNK_BYTES`, `vaultCheckpointTag`, `vaultContentHash`, `parseVaultCheckpoint`, `matchesVaultChunk`, `vaultCheckpointShapedTag`; types `VaultCheckpoint`, `VaultChunkRef`.
|
|
119
|
+
- Private vault recovery: `readVaultSnapshot`, `readVaultRotations`, `readVaultHeads`, `readVaultHeadRotations`, `VAULT_CLOCK_TOLERANCE_SECONDS`; types `VaultReadResult`, `VaultReader`, `VaultHeadsResult`.
|
|
120
|
+
- Private vault relay reads: `createVaultRelayReader`, `fetchVaultEvents`, `MAX_VAULT_EVENTS_PER_RELAY`, `VaultRelayError`.
|
|
121
|
+
- Bot ownership profile (see `docs/bot-ownership-v1.md`): `BOT_OWNERSHIP_TYPE`, `BOT_OWNERSHIP_POLICY`, `buildBotOwnership`, `buildBotOwnershipRevocation`, `readBotOwnership`, `readBotOwnershipSync`, `botOwnershipRenewalDue`; types `BotOwnershipClaim`, `BotOwnershipResult`.
|
|
122
|
+
|
|
123
|
+
The voting extension (kinds 30482–30484) is specified in `spec/voting.md` and implemented in the separate `nostr-anon-vote` package on npm, not in `signet-protocol`.
|
|
124
|
+
|
|
125
|
+
## Optional
|
|
126
|
+
|
|
127
|
+
- [llms-full.txt](llms-full.txt): llms.txt plus the full protocol and voting specifications and the developer guides in one file
|
|
128
|
+
- [Full protocol specification](spec/protocol.md)
|
|
129
|
+
- [Voting extension specification](spec/voting.md)
|
|
130
|
+
- [Signet in 5 Minutes: developer overview](docs/signet-in-5-minutes.md)
|
|
131
|
+
- [Implementation levels guide](docs/implementation-levels.md)
|
|
132
|
+
- [Full flow example](examples/full-flow.ts)
|
|
133
|
+
- [Jurisdiction-specific examples](examples/jurisdictions/)
|
|
134
|
+
- [Regulatory compatibility](README.md#regulatory-compatibility)
|
package/package.json
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "signet-protocol",
|
|
3
|
-
"version": "1.11.
|
|
3
|
+
"version": "1.11.1",
|
|
4
4
|
"description": "Decentralised identity verification protocol for Nostr",
|
|
5
|
+
"homepage": "https://github.com/forgesworn/signet#readme",
|
|
5
6
|
"type": "module",
|
|
6
7
|
"main": "./dist/index.js",
|
|
7
8
|
"types": "./dist/index.d.ts",
|
|
@@ -17,7 +18,9 @@
|
|
|
17
18
|
},
|
|
18
19
|
"files": [
|
|
19
20
|
"dist",
|
|
20
|
-
"src"
|
|
21
|
+
"src",
|
|
22
|
+
"llms.txt",
|
|
23
|
+
"llms-full.txt"
|
|
21
24
|
],
|
|
22
25
|
"publishConfig": {
|
|
23
26
|
"provenance": true
|
|
@@ -29,7 +32,9 @@
|
|
|
29
32
|
"typecheck": "tsc --noEmit",
|
|
30
33
|
"clean": "rm -rf dist",
|
|
31
34
|
"prepare": "npm run build",
|
|
32
|
-
"
|
|
35
|
+
"build:llms": "node scripts/build-llms-full.mjs",
|
|
36
|
+
"check:llms": "node scripts/build-llms-full.mjs --check",
|
|
37
|
+
"prepublishOnly": "npm run build:llms && npm run typecheck && npm test && npm run build"
|
|
33
38
|
},
|
|
34
39
|
"keywords": [
|
|
35
40
|
"nostr",
|