signet-protocol 1.11.1 → 1.11.3
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 +8 -1
- package/dist/badge.d.ts +5 -1
- package/dist/badge.d.ts.map +1 -1
- package/dist/badge.js +15 -6
- package/dist/badge.js.map +1 -1
- package/dist/verifiers.d.ts +13 -2
- package/dist/verifiers.d.ts.map +1 -1
- package/dist/verifiers.js +15 -2
- package/dist/verifiers.js.map +1 -1
- package/docs/bot-ownership-v1.md +43 -0
- package/docs/implementation-levels.md +235 -0
- package/docs/private-vault-v1.md +104 -0
- package/docs/signet-in-5-minutes.md +181 -0
- package/examples/full-flow.ts +260 -0
- package/examples/jurisdictions/brazil-lawyer.ts +186 -0
- package/examples/jurisdictions/eu-doctor.ts +160 -0
- package/examples/jurisdictions/india-doctor.ts +207 -0
- package/examples/jurisdictions/japan-notary.ts +157 -0
- package/examples/jurisdictions/multi-jurisdiction.ts +174 -0
- package/examples/jurisdictions/uae-professional.ts +190 -0
- package/examples/jurisdictions/uk-solicitor.ts +170 -0
- package/examples/jurisdictions/us-attorney.ts +161 -0
- package/examples/signet-me.ts +253 -0
- package/llms-full.txt +53 -20
- package/llms.txt +29 -11
- package/package.json +7 -1
- package/spec/protocol.md +2630 -0
- package/spec/voting.md +563 -0
- package/src/badge.ts +22 -8
- package/src/verifiers.ts +18 -2
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
# Signet in 5 Minutes
|
|
2
|
+
|
|
3
|
+
Signet is an open-source, decentralised identity verification protocol for Nostr. It lets users prove claims
|
|
4
|
+
about who they are — human, professional, adult, organisation — without exposing personal data. Verification
|
|
5
|
+
is anchored to professional bodies (law societies, medical boards, notary commissions) rather than any central
|
|
6
|
+
authority. Trust accumulates as a continuous Signet Score (0–200, where 100 represents the current government standard) assembled from weighted signals, and is expressed as a tier badge any Nostr client can display.
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
## How Trust Flows
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
Person
|
|
13
|
+
|
|
|
14
|
+
| signs a kind 31000 self-declaration (`type: credential`)
|
|
15
|
+
v
|
|
16
|
+
Tier 1 — Self-declared
|
|
17
|
+
|
|
|
18
|
+
| three or more peers sign kind 31000 vouches (`type: vouch`)
|
|
19
|
+
v
|
|
20
|
+
Tier 2 — Web-of-trust
|
|
21
|
+
|
|
|
22
|
+
| licensed professional issues kind 31000 credential (`type: credential`)
|
|
23
|
+
| with zero-knowledge age proof and document nullifier
|
|
24
|
+
v
|
|
25
|
+
Tier 3 — Professionally verified (adult)
|
|
26
|
+
|
|
|
27
|
+
| same ceremony, scope extended to child supervision
|
|
28
|
+
v
|
|
29
|
+
Tier 4 — Professionally verified (adult + child)
|
|
30
|
+
|
|
|
31
|
+
v
|
|
32
|
+
Trust signals accumulated (professional verif, vouches, account age, identity bridges)
|
|
33
|
+
|
|
|
34
|
+
v
|
|
35
|
+
Signet Score computed (0-200, where 100 = government standard)
|
|
36
|
+
|
|
|
37
|
+
v
|
|
38
|
+
Badge displayed by client e.g. "Verified (Tier 3) — Signet Score 106"
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
## The Four Tiers
|
|
43
|
+
|
|
44
|
+
| Tier | Name | Who issues | What it proves | Score contribution |
|
|
45
|
+
|------|-----------------------------|-----------------------------------------|-----------------------------------------------------|--------------------|
|
|
46
|
+
| 1 | Self-declared | Subject themselves | "I exist and I own this key" | — |
|
|
47
|
+
| 2 | Web-of-trust | Peers (kind 31000, `type: vouch`) | Enough known people can vouch for this identity | +2–8 per vouch |
|
|
48
|
+
| 3 | Professionally verified | Licensed professional (kind 31000, `type: credential`) | Government document seen; adult age confirmed | +40 |
|
|
49
|
+
| 4 | Professional + child safety | Licensed professional (kind 31000, `type: credential`) | Tier 3 plus authorisation to interact with minors | +40 |
|
|
50
|
+
|
|
51
|
+
Tier 2 requires at least 3 vouches from Tier 2+ peers (configurable per community via kind 30078 (NIP-78) policy).
|
|
52
|
+
Tiers 3 and 4 are issued in a two-credential ceremony: one Natural Person credential (carries document
|
|
53
|
+
nullifier and Merkle root) and one anonymous Persona credential (carries age range only, no identifying data).
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
## Three Implementation Levels
|
|
57
|
+
|
|
58
|
+
### Level 1 — Display badges (a weekend)
|
|
59
|
+
|
|
60
|
+
Read kind 31000 (`type: credential` and `type: vouch`) from relays, call `computeBadge`, show a label.
|
|
61
|
+
No cryptography required beyond Schnorr signature verification (optional).
|
|
62
|
+
|
|
63
|
+
### Level 2 — Issue vouches (a few days)
|
|
64
|
+
|
|
65
|
+
Add kind 31000 (`type: vouch`) vouch issuance. Requires Schnorr signing, relay write access, and basic
|
|
66
|
+
kind 30078 (NIP-78) policy parsing so your client respects community verification thresholds.
|
|
67
|
+
|
|
68
|
+
### Level 3 — Full protocol (weeks)
|
|
69
|
+
|
|
70
|
+
Issue professional credentials, manage verifier lifecycle (kind 31000 types: verifier, challenge, revocation), build
|
|
71
|
+
guardian delegation (kind 31000, `type: delegation`), support credential chains (supersedes / superseded-by),
|
|
72
|
+
and integrate the voting extension (kinds 30482–30484). The voting extension is specified in `spec/voting.md` and implemented in the separate `nostr-anon-vote` package, not in `signet-protocol`; this package ships only the linkable ring signature primitives it builds on (`lsagSign`, `lsagVerify`, `computeKeyImage`).
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
## How Verification Works (Tier 3 / 4)
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
Subject Verifier
|
|
79
|
+
| |
|
|
80
|
+
|--- presents document -->|
|
|
81
|
+
| |
|
|
82
|
+
| Verifier computes nullifier:
|
|
83
|
+
| SHA-256(docType || country || docNumber || salt)
|
|
84
|
+
| (prevents duplicate identity; document never stored)
|
|
85
|
+
| |
|
|
86
|
+
| Verifier builds Merkle tree:
|
|
87
|
+
| leaves = verified attributes (name, DOB, address, ...)
|
|
88
|
+
| only root is published on-chain
|
|
89
|
+
| |
|
|
90
|
+
| Verifier creates zero-knowledge age range proof
|
|
91
|
+
| (proves "subject is 18+" without revealing birth date)
|
|
92
|
+
| |
|
|
93
|
+
| Two credentials signed and published:
|
|
94
|
+
| kind 31000 Natural Person (nullifier + Merkle root, type: credential)
|
|
95
|
+
| kind 31000 Persona (age range only, anonymous, type: credential)
|
|
96
|
+
| |
|
|
97
|
+
v v
|
|
98
|
+
Both events published to Nostr relays
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Professional verifiers are themselves credentialed (kind 31000, `type: verifier`) and require cross-verification
|
|
102
|
+
by two other licensed professionals before becoming active. A challenge / revocation mechanism
|
|
103
|
+
(kind 31000 types: `challenge` and `revocation`) allows the community to remove a fraudulent verifier.
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
## Event Kinds Reference
|
|
107
|
+
|
|
108
|
+
| Kind | Type tag | Purpose |
|
|
109
|
+
|-------|-------------------|----------------------------------------------------|
|
|
110
|
+
| 31000 | `credential` | Verification credential for any tier |
|
|
111
|
+
| 31000 | `vouch` | Peer attestation (Tier 2 building block) |
|
|
112
|
+
| 30078 | — | Policy: community verification requirements (NIP-78) |
|
|
113
|
+
| 31000 | `verifier` | Professional verifier credential |
|
|
114
|
+
| 31000 | `challenge` | Report a suspected fraudulent verifier |
|
|
115
|
+
| 31000 | `revocation` | Remove a verifier after threshold confirmations |
|
|
116
|
+
| 31000 | `identity-bridge` | Link two keypairs via ring signature |
|
|
117
|
+
| 31000 | `delegation` | Guardian / agent delegation with scoped permission |
|
|
118
|
+
| 30482 | — | Voting extension: define an election |
|
|
119
|
+
| 30483 | — | Voting extension: cast an anonymous ballot |
|
|
120
|
+
| 30484 | — | Voting extension: publish tallied result |
|
|
121
|
+
|
|
122
|
+
Kinds 30482–30484 are specified in `spec/voting.md` and implemented in the separate `nostr-anon-vote` package, not in `signet-protocol`.
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
## Integrating at Level 1
|
|
126
|
+
|
|
127
|
+
Install the library:
|
|
128
|
+
|
|
129
|
+
```
|
|
130
|
+
npm install signet-protocol
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Fetch events from a relay, then call `computeBadge`:
|
|
134
|
+
|
|
135
|
+
```typescript
|
|
136
|
+
import { computeBadge, buildBadgeFilters, fetchFromRelay, meetsMinimumTier } from 'signet-protocol';
|
|
137
|
+
import type { NostrEvent } from 'signet-protocol';
|
|
138
|
+
|
|
139
|
+
// 1. Build relay filters for one or more pubkeys
|
|
140
|
+
const pubkeys = ['<hex-pubkey>'];
|
|
141
|
+
const filters = buildBadgeFilters(pubkeys);
|
|
142
|
+
// filters = [{ kinds: [31000], '#d': ['credential:<hex-pubkey>', 'vouch:<hex-pubkey>'] }]
|
|
143
|
+
|
|
144
|
+
// 2. Fetch events from your relay (fetchFromRelay is the package's own client; any Nostr client works)
|
|
145
|
+
const events: NostrEvent[] = await fetchFromRelay('wss://relay.example.com', filters);
|
|
146
|
+
|
|
147
|
+
// 3. Compute the badge
|
|
148
|
+
const badge = await computeBadge(pubkeys[0], events, { verifySignatures: true });
|
|
149
|
+
|
|
150
|
+
// badge.displayLabel => "Verified (Tier 3)"
|
|
151
|
+
// badge.score => 106 (illustrative; see the score note below)
|
|
152
|
+
// badge.tier => 3
|
|
153
|
+
// badge.vouchCount => 5
|
|
154
|
+
|
|
155
|
+
// 4. Gate a feature on minimum tier
|
|
156
|
+
if (meetsMinimumTier(badge, 2)) {
|
|
157
|
+
// allow posting in a verified-only community
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
`computeBadge` accepts any mix of kind 31000 (`type: credential` and `type: vouch`) events. It handles expiry, deduplication
|
|
162
|
+
(one vouch counted per voucher pubkey), and optional Schnorr signature verification. The result is
|
|
163
|
+
a plain object — no rendering assumptions are made.
|
|
164
|
+
|
|
165
|
+
- `verifySignatures` defaults to `false`. `computeBadge(pubkey, events)` with no options checks no signatures, so keep `{ verifySignatures: true }` unless the events were already verified. `fetchFromRelay` drops events with an invalid signature or id by default; events from any other source are not checked for you.
|
|
166
|
+
- `badge.score` is on the 0–200 Signet IQ scale but is a Level 1 approximation of `computeTrustScore(...).score`: it ignores account age and identity bridges and applies no per-type caps (1 professional verification, 3 in-person vouches, 5 online vouches). Use `computeTrustScore` for the full score.
|
|
167
|
+
- `computeBadge` checks who issued a Tier 3 or Tier 4 credential only when you pass `isTrustedIssuer`, and `buildBadgeFilters` does not fetch verifier or revocation events. Without the option, any key can sign an event that carries `tier: 3` and it will count. To require an activated, unrevoked verifier, fetch the verifier credentials, their vouches and revocations yourself, decide which issuers pass with `checkCrossVerification(...).activated` and `isVerifierRevoked(...)`, and pass `{ isTrustedIssuer: (pk) => accepted.has(pk) }` to `computeBadge` (see `llms.txt`).
|
|
168
|
+
|
|
169
|
+
The `buildBadgeFilters` helper returns a standard Nostr REQ filter array ready for use with any
|
|
170
|
+
relay client library.
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
## Further Reading
|
|
174
|
+
|
|
175
|
+
- Full protocol specification: `spec/protocol.md`
|
|
176
|
+
- Voting extension: `spec/voting.md`
|
|
177
|
+
- Level 1 badge library: `src/badge.ts`
|
|
178
|
+
- Signet Score details: `src/trust-score.ts`
|
|
179
|
+
- Full working example: `examples/full-flow.ts`
|
|
180
|
+
- Jurisdiction-specific examples: `examples/jurisdictions/`
|
|
181
|
+
- "Signet me" peer verification: `examples/signet-me.ts`
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
// Signet Protocol — Full Flow Example
|
|
2
|
+
// Demonstrates all 4 tiers, Signet IQ, policy enforcement, and verifier lifecycle
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
generateKeyPair,
|
|
6
|
+
// Credentials
|
|
7
|
+
createSelfDeclaredCredential,
|
|
8
|
+
createPeerVouchedCredential,
|
|
9
|
+
createProfessionalCredential,
|
|
10
|
+
createChildSafetyCredential,
|
|
11
|
+
verifyCredential,
|
|
12
|
+
parseCredential,
|
|
13
|
+
// Vouches
|
|
14
|
+
createVouch,
|
|
15
|
+
hasEnoughVouches,
|
|
16
|
+
// Signet IQ
|
|
17
|
+
computeTrustScore,
|
|
18
|
+
formatTrustDisplay,
|
|
19
|
+
// Policies
|
|
20
|
+
createPolicy,
|
|
21
|
+
PolicyChecker,
|
|
22
|
+
// Verifiers
|
|
23
|
+
createVerifierCredential,
|
|
24
|
+
checkCrossVerification,
|
|
25
|
+
isVerifierRevoked,
|
|
26
|
+
// Challenges
|
|
27
|
+
createChallenge,
|
|
28
|
+
createRevocation,
|
|
29
|
+
// Merkle
|
|
30
|
+
MerkleTree,
|
|
31
|
+
verifySelectiveDisclosure,
|
|
32
|
+
} from '../src/index.js';
|
|
33
|
+
|
|
34
|
+
async function main() {
|
|
35
|
+
console.log('=== Signet Protocol — Full Flow Demo ===\n');
|
|
36
|
+
|
|
37
|
+
// --- 1. Generate identities ---
|
|
38
|
+
const alice = generateKeyPair(); // Regular user
|
|
39
|
+
const bob = generateKeyPair(); // Regular user
|
|
40
|
+
const carol = generateKeyPair(); // Regular user
|
|
41
|
+
const dave = generateKeyPair(); // Regular user
|
|
42
|
+
const emma = generateKeyPair(); // Parent
|
|
43
|
+
const lawyer = generateKeyPair(); // Professional verifier (solicitor)
|
|
44
|
+
const doctor = generateKeyPair(); // Professional verifier (doctor)
|
|
45
|
+
const notary = generateKeyPair(); // Professional verifier (notary)
|
|
46
|
+
const operator = generateKeyPair(); // Community operator
|
|
47
|
+
|
|
48
|
+
console.log('--- Tier 1: Self-Declared ---');
|
|
49
|
+
const aliceSelfCred = await createSelfDeclaredCredential(alice.privateKey);
|
|
50
|
+
const verification = await verifyCredential(aliceSelfCred);
|
|
51
|
+
console.log(`Alice self-declares: sig=${verification.signatureValid}, valid=${verification.structureValid}`);
|
|
52
|
+
console.log(` Tier: ${parseCredential(aliceSelfCred)!.tier}, Type: ${parseCredential(aliceSelfCred)!.type}\n`);
|
|
53
|
+
|
|
54
|
+
// --- 2. Web-of-Trust Vouching (Tier 2) ---
|
|
55
|
+
console.log('--- Tier 2: Web-of-Trust ---');
|
|
56
|
+
|
|
57
|
+
// Bob, Carol, Dave vouch for Alice (met at a Bitcoin meetup)
|
|
58
|
+
const vouches = await Promise.all([
|
|
59
|
+
createVouch(bob.privateKey, {
|
|
60
|
+
subjectPubkey: alice.publicKey,
|
|
61
|
+
method: 'in-person',
|
|
62
|
+
context: 'bitcoin-meetup-london',
|
|
63
|
+
voucherTier: 2,
|
|
64
|
+
voucherScore: 60,
|
|
65
|
+
}),
|
|
66
|
+
createVouch(carol.privateKey, {
|
|
67
|
+
subjectPubkey: alice.publicKey,
|
|
68
|
+
method: 'in-person',
|
|
69
|
+
context: 'bitcoin-meetup-london',
|
|
70
|
+
voucherTier: 2,
|
|
71
|
+
voucherScore: 55,
|
|
72
|
+
}),
|
|
73
|
+
createVouch(dave.privateKey, {
|
|
74
|
+
subjectPubkey: alice.publicKey,
|
|
75
|
+
method: 'online',
|
|
76
|
+
voucherTier: 2,
|
|
77
|
+
voucherScore: 45,
|
|
78
|
+
}),
|
|
79
|
+
]);
|
|
80
|
+
|
|
81
|
+
const enough = hasEnoughVouches(vouches, alice.publicKey);
|
|
82
|
+
console.log(`Alice has enough vouches for Tier 2: ${enough}`); // true (3 vouches, threshold = 3)
|
|
83
|
+
|
|
84
|
+
// Issue Tier 2 credential
|
|
85
|
+
const aliceTier2 = await createPeerVouchedCredential(operator.privateKey, alice.publicKey);
|
|
86
|
+
console.log(`Alice promoted to Tier 2: ${parseCredential(aliceTier2)!.tier}\n`);
|
|
87
|
+
|
|
88
|
+
// --- 3. Professional Verifier Setup ---
|
|
89
|
+
console.log('--- Verifier Registration ---');
|
|
90
|
+
|
|
91
|
+
const lawyerCred = await createVerifierCredential(lawyer.privateKey, {
|
|
92
|
+
profession: 'solicitor',
|
|
93
|
+
jurisdiction: 'UK',
|
|
94
|
+
licenceHash: 'sha256_of_sra_number_12345',
|
|
95
|
+
professionalBody: 'Law Society of England and Wales',
|
|
96
|
+
statement: 'Available for Signet identity verification in London',
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
const doctorCred = await createVerifierCredential(doctor.privateKey, {
|
|
100
|
+
profession: 'doctor',
|
|
101
|
+
jurisdiction: 'UK',
|
|
102
|
+
licenceHash: 'sha256_of_gmc_number_67890',
|
|
103
|
+
professionalBody: 'General Medical Council',
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
const notaryCred = await createVerifierCredential(notary.privateKey, {
|
|
107
|
+
profession: 'notary',
|
|
108
|
+
jurisdiction: 'UK',
|
|
109
|
+
licenceHash: 'sha256_of_notary_commission_11111',
|
|
110
|
+
professionalBody: 'Notaries Society',
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
// Cross-verify: doctor and notary both vouch for the lawyer (2 different professions)
|
|
114
|
+
const crossVouches = await Promise.all([
|
|
115
|
+
createVouch(doctor.privateKey, {
|
|
116
|
+
subjectPubkey: lawyer.publicKey,
|
|
117
|
+
method: 'in-person',
|
|
118
|
+
voucherTier: 3,
|
|
119
|
+
voucherScore: 90,
|
|
120
|
+
}),
|
|
121
|
+
createVouch(notary.privateKey, {
|
|
122
|
+
subjectPubkey: lawyer.publicKey,
|
|
123
|
+
method: 'in-person',
|
|
124
|
+
voucherTier: 3,
|
|
125
|
+
voucherScore: 85,
|
|
126
|
+
}),
|
|
127
|
+
]);
|
|
128
|
+
|
|
129
|
+
const lawyerActivation = checkCrossVerification(
|
|
130
|
+
lawyer.publicKey,
|
|
131
|
+
crossVouches,
|
|
132
|
+
[lawyerCred, doctorCred, notaryCred]
|
|
133
|
+
);
|
|
134
|
+
console.log(`Lawyer activated: ${lawyerActivation.activated}`);
|
|
135
|
+
console.log(` Vouches: ${lawyerActivation.vouchCount}, Professions: ${lawyerActivation.professions.join(', ')}\n`);
|
|
136
|
+
|
|
137
|
+
// --- 4. Professional Verification (Tier 3) ---
|
|
138
|
+
console.log('--- Tier 3: Professional Verification ---');
|
|
139
|
+
|
|
140
|
+
const aliceTier3 = await createProfessionalCredential(lawyer.privateKey, alice.publicKey, {
|
|
141
|
+
profession: 'solicitor',
|
|
142
|
+
jurisdiction: 'UK',
|
|
143
|
+
proofBlob: '(ZKP proof would go here)',
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
const tier3Result = await verifyCredential(aliceTier3);
|
|
147
|
+
console.log(`Alice Tier 3: sig=${tier3Result.signatureValid}, valid=${tier3Result.structureValid}`);
|
|
148
|
+
console.log(` Parsed: tier=${parseCredential(aliceTier3)!.tier}, profession=${parseCredential(aliceTier3)!.profession}\n`);
|
|
149
|
+
|
|
150
|
+
// --- 5. Child Safety (Tier 4) ---
|
|
151
|
+
console.log('--- Tier 4: Child Safety ---');
|
|
152
|
+
|
|
153
|
+
const emmaTier4 = await createChildSafetyCredential(lawyer.privateKey, emma.publicKey, {
|
|
154
|
+
profession: 'solicitor',
|
|
155
|
+
jurisdiction: 'UK',
|
|
156
|
+
ageRange: '8-12',
|
|
157
|
+
proofBlob: '(Bulletproof range proof would go here)',
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
const tier4Result = await verifyCredential(emmaTier4);
|
|
161
|
+
const parsed4 = parseCredential(emmaTier4)!;
|
|
162
|
+
console.log(`Emma Tier 4: sig=${tier4Result.signatureValid}, valid=${tier4Result.structureValid}`);
|
|
163
|
+
console.log(` Scope: ${parsed4.scope}, Age range: ${parsed4.ageRange}\n`);
|
|
164
|
+
|
|
165
|
+
// --- 6. Signet IQ ---
|
|
166
|
+
console.log('--- Signet IQ ---');
|
|
167
|
+
|
|
168
|
+
const oneYearAgo = Math.floor(Date.now() / 1000) - 365 * 24 * 60 * 60;
|
|
169
|
+
const aliceScore = computeTrustScore(
|
|
170
|
+
alice.publicKey,
|
|
171
|
+
[aliceSelfCred, aliceTier2, aliceTier3],
|
|
172
|
+
vouches,
|
|
173
|
+
oneYearAgo
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
console.log(formatTrustDisplay(aliceScore));
|
|
177
|
+
console.log();
|
|
178
|
+
|
|
179
|
+
// --- 7. Selective Disclosure (Merkle) ---
|
|
180
|
+
console.log('--- Selective Disclosure ---');
|
|
181
|
+
|
|
182
|
+
const tree = new MerkleTree({
|
|
183
|
+
tier: '3',
|
|
184
|
+
type: 'professional',
|
|
185
|
+
scope: 'adult',
|
|
186
|
+
profession: 'solicitor',
|
|
187
|
+
jurisdiction: 'UK',
|
|
188
|
+
method: 'in-person-id',
|
|
189
|
+
subject: alice.publicKey,
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
// Alice only reveals tier and jurisdiction
|
|
193
|
+
const disclosure = tree.disclose(['tier', 'jurisdiction']);
|
|
194
|
+
console.log(`Merkle root: ${tree.getRoot().slice(0, 16)}...`);
|
|
195
|
+
console.log(`Revealed: ${JSON.stringify(disclosure.revealedAttributes)}`);
|
|
196
|
+
console.log(`Disclosure valid: ${verifySelectiveDisclosure(disclosure)}\n`);
|
|
197
|
+
|
|
198
|
+
// --- 8. Community Policy ---
|
|
199
|
+
console.log('--- Policy Enforcement ---');
|
|
200
|
+
|
|
201
|
+
const policyEvent = await createPolicy(operator.privateKey, {
|
|
202
|
+
communityId: 'safe-kids-learning',
|
|
203
|
+
adultMinTier: 3,
|
|
204
|
+
childMinTier: 4,
|
|
205
|
+
enforcement: 'both',
|
|
206
|
+
minScore: 50,
|
|
207
|
+
modMinTier: 3,
|
|
208
|
+
verifierBond: 100000,
|
|
209
|
+
revocationThreshold: 5,
|
|
210
|
+
description: 'Maximum safety space for children',
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
const checker = new PolicyChecker(policyEvent);
|
|
214
|
+
|
|
215
|
+
console.log(`Alice (Tier 3, score ${aliceScore.score}) as adult: ${checker.checkAdult(3, aliceScore.score).allowed}`);
|
|
216
|
+
console.log(`Emma (Tier 4, score 60) as child account: ${checker.checkChild(4, 60).allowed}`);
|
|
217
|
+
console.log(`Bob (Tier 2, score 40) as adult: ${checker.checkAdult(2, 40).allowed}`);
|
|
218
|
+
|
|
219
|
+
const bobResult = checker.checkAdult(2, 40);
|
|
220
|
+
if (!bobResult.allowed) {
|
|
221
|
+
console.log(` Reason: ${bobResult.reason}`);
|
|
222
|
+
}
|
|
223
|
+
console.log();
|
|
224
|
+
|
|
225
|
+
// --- 9. Challenge & Revocation ---
|
|
226
|
+
console.log('--- Challenge & Revocation ---');
|
|
227
|
+
|
|
228
|
+
const rogue = generateKeyPair();
|
|
229
|
+
const rogueCred = await createVerifierCredential(rogue.privateKey, {
|
|
230
|
+
profession: 'solicitor',
|
|
231
|
+
jurisdiction: 'UK',
|
|
232
|
+
licenceHash: 'fake_licence',
|
|
233
|
+
professionalBody: 'Law Society',
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
const challenge = await createChallenge(alice.privateKey, {
|
|
237
|
+
verifierPubkey: rogue.publicKey,
|
|
238
|
+
reason: 'registry-mismatch',
|
|
239
|
+
evidenceType: 'registry-screenshot',
|
|
240
|
+
reporterTier: 3,
|
|
241
|
+
evidence: 'SRA lookup: licence number not found in registry',
|
|
242
|
+
});
|
|
243
|
+
console.log(`Challenge filed against rogue verifier`);
|
|
244
|
+
|
|
245
|
+
const revocation = await createRevocation(operator.privateKey, {
|
|
246
|
+
verifierPubkey: rogue.publicKey,
|
|
247
|
+
challengeEventId: challenge.id,
|
|
248
|
+
confirmations: 7,
|
|
249
|
+
bondAction: 'slashed',
|
|
250
|
+
scope: 'full',
|
|
251
|
+
effectiveAt: Math.floor(Date.now() / 1000),
|
|
252
|
+
summary: 'Licence number confirmed fake via SRA registry check',
|
|
253
|
+
});
|
|
254
|
+
console.log(`Revocation issued: ${isVerifierRevoked(rogue.publicKey, [revocation])}`);
|
|
255
|
+
console.log();
|
|
256
|
+
|
|
257
|
+
console.log('=== Demo Complete ===');
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
main().catch(console.error);
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
// Signet Protocol — Brazilian Lawyer Example
|
|
2
|
+
// Demonstrates identity verification under LGPD (Lei Geral de Protecao de Dados)
|
|
3
|
+
// Jurisdiction: Brazil (BR)
|
|
4
|
+
// Profession: Lawyer regulated by the OAB (Ordem dos Advogados do Brasil)
|
|
5
|
+
// Features: LGPD compliance, digital credential support, child protection under Art. 14
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
generateKeyPair,
|
|
9
|
+
getJurisdiction,
|
|
10
|
+
getProfessionalBodies,
|
|
11
|
+
createProfessionalCredential,
|
|
12
|
+
createChildSafetyCredential,
|
|
13
|
+
verifyCredential,
|
|
14
|
+
parseCredential,
|
|
15
|
+
checkCredentialCompliance,
|
|
16
|
+
checkChildCompliance,
|
|
17
|
+
getConsentRequirements,
|
|
18
|
+
getRetentionGuidance,
|
|
19
|
+
setLanguage,
|
|
20
|
+
t,
|
|
21
|
+
getTierDescription,
|
|
22
|
+
formatLocalizedTrustScore,
|
|
23
|
+
computeTrustScore,
|
|
24
|
+
} from '../../src/index.js';
|
|
25
|
+
|
|
26
|
+
async function main() {
|
|
27
|
+
console.log('=== Protocolo Signet — Verificacao de Advogado Brasileiro ===\n');
|
|
28
|
+
|
|
29
|
+
// Set language to Portuguese
|
|
30
|
+
setLanguage('pt');
|
|
31
|
+
|
|
32
|
+
// --- 1. Jurisdiction and OAB lookup ---
|
|
33
|
+
const br = getJurisdiction('BR');
|
|
34
|
+
if (!br) throw new Error('BR jurisdiction not found');
|
|
35
|
+
|
|
36
|
+
console.log(`Jurisdicao: ${br.name} (${br.nameLocal})`);
|
|
37
|
+
console.log(`Sistema juridico: ${br.legalSystem}`);
|
|
38
|
+
console.log(`Protecao de dados: ${br.dataProtection.name} - ${br.dataProtection.fullName}`);
|
|
39
|
+
console.log(`Autoridade supervisora: ${br.dataProtection.supervisoryAuthority}`);
|
|
40
|
+
if (br.dataProtection.notes) {
|
|
41
|
+
console.log(`Nota: ${br.dataProtection.notes}`);
|
|
42
|
+
}
|
|
43
|
+
console.log();
|
|
44
|
+
|
|
45
|
+
// Look up the OAB — notably, it issues digital credentials via ICP-Brasil
|
|
46
|
+
const legalBodies = getProfessionalBodies('BR', 'legal');
|
|
47
|
+
console.log('Entidades profissionais juridicas no Brasil:');
|
|
48
|
+
for (const body of legalBodies) {
|
|
49
|
+
console.log(` - ${body.name}`);
|
|
50
|
+
if (body.nameEn) console.log(` (${body.nameEn})`);
|
|
51
|
+
console.log(` Website: ${body.website}`);
|
|
52
|
+
console.log(` Registro publico: ${body.hasPublicRegister}`);
|
|
53
|
+
if (body.registerUrl) {
|
|
54
|
+
console.log(` URL do registro: ${body.registerUrl}`);
|
|
55
|
+
}
|
|
56
|
+
// OAB issues digital certificates via ICP-Brasil PKI
|
|
57
|
+
console.log(` Credenciais digitais: ${body.issuesDigitalCredentials}`);
|
|
58
|
+
if (body.notes) {
|
|
59
|
+
console.log(` Nota: ${body.notes}`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
console.log();
|
|
63
|
+
|
|
64
|
+
// --- 2. Generate identities ---
|
|
65
|
+
const advogado = generateKeyPair(); // The verifying lawyer (advogado)
|
|
66
|
+
const subject = generateKeyPair(); // Person being verified
|
|
67
|
+
const parent = generateKeyPair(); // Parent (for child credential)
|
|
68
|
+
|
|
69
|
+
// --- 3. Create Tier 3 credential (Portuguese output) ---
|
|
70
|
+
console.log(`--- ${t('tier_3')}: ${t('credential_professional')} ---`);
|
|
71
|
+
console.log(getTierDescription(3));
|
|
72
|
+
console.log();
|
|
73
|
+
|
|
74
|
+
const credential = await createProfessionalCredential(
|
|
75
|
+
advogado.privateKey,
|
|
76
|
+
subject.publicKey,
|
|
77
|
+
{
|
|
78
|
+
profession: 'lawyer',
|
|
79
|
+
jurisdiction: 'BR',
|
|
80
|
+
}
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
const verification = await verifyCredential(credential);
|
|
84
|
+
const parsed = parseCredential(credential)!;
|
|
85
|
+
|
|
86
|
+
console.log(`${t('credential_professional')}: ${t('status_valid')}`);
|
|
87
|
+
console.log(` ${t('msg_signature_valid')}`);
|
|
88
|
+
console.log(` Tier: ${parsed.tier}, Profissao: ${parsed.profession}`);
|
|
89
|
+
console.log(` Jurisdicao: ${parsed.jurisdiction}`);
|
|
90
|
+
console.log();
|
|
91
|
+
|
|
92
|
+
// --- 4. LGPD compliance check ---
|
|
93
|
+
// Brazil's LGPD is modelled on the GDPR and requires explicit consent.
|
|
94
|
+
// ICP-Brasil provides the PKI infrastructure for digital signatures.
|
|
95
|
+
console.log('--- Verificacao de conformidade LGPD ---');
|
|
96
|
+
const compliance = checkCredentialCompliance(credential, 'BR');
|
|
97
|
+
console.log(`Em conformidade: ${compliance.compliant}`);
|
|
98
|
+
|
|
99
|
+
for (const issue of compliance.issues) {
|
|
100
|
+
const prefix = issue.severity === 'error' ? '[ERRO]'
|
|
101
|
+
: issue.severity === 'warning' ? '[AVISO]'
|
|
102
|
+
: '[INFO]';
|
|
103
|
+
console.log(` ${prefix} ${issue.message}`);
|
|
104
|
+
console.log(` Regulamento: ${issue.regulation}`);
|
|
105
|
+
if (issue.remediation) {
|
|
106
|
+
console.log(` Acao: ${issue.remediation}`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
console.log();
|
|
110
|
+
|
|
111
|
+
// --- 5. LGPD consent requirements ---
|
|
112
|
+
// LGPD treats biometric data as sensitive personal data requiring specific legal basis.
|
|
113
|
+
console.log('--- Requisitos de consentimento ---');
|
|
114
|
+
const consent = getConsentRequirements('BR');
|
|
115
|
+
console.log(`Consentimento explicito: ${consent.requiresExplicitConsent}`);
|
|
116
|
+
console.log(`Idade de consentimento digital: ${consent.consentAge}`);
|
|
117
|
+
console.log(`Categorias de dados: ${consent.dataCategories.join(', ')}`);
|
|
118
|
+
if (consent.specialCategories.length > 0) {
|
|
119
|
+
console.log(`Categorias especiais: ${consent.specialCategories.join(', ')}`);
|
|
120
|
+
}
|
|
121
|
+
if (consent.notes) {
|
|
122
|
+
console.log(`Nota: ${consent.notes}`);
|
|
123
|
+
}
|
|
124
|
+
console.log();
|
|
125
|
+
|
|
126
|
+
// --- 6. Child protection under LGPD Art. 14 ---
|
|
127
|
+
// LGPD Art. 14 provides specific rules for processing children's data.
|
|
128
|
+
// Brazil's digital consent age is 12 (one of the lowest globally).
|
|
129
|
+
// The ECA (Estatuto da Crianca e do Adolescente) provides additional protections.
|
|
130
|
+
console.log('--- Protecao infantil — LGPD Art. 14 + ECA ---');
|
|
131
|
+
|
|
132
|
+
const childCredential = await createChildSafetyCredential(
|
|
133
|
+
advogado.privateKey,
|
|
134
|
+
parent.publicKey,
|
|
135
|
+
{
|
|
136
|
+
profession: 'lawyer',
|
|
137
|
+
jurisdiction: 'BR',
|
|
138
|
+
ageRange: '8-12',
|
|
139
|
+
}
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
const childVerification = await verifyCredential(childCredential);
|
|
143
|
+
const childParsed = parseCredential(childCredential)!;
|
|
144
|
+
|
|
145
|
+
console.log(`${t('credential_professional_child')}: ${t('status_valid')}`);
|
|
146
|
+
console.log(` Escopo: ${childParsed.scope}, Faixa etaria: ${childParsed.ageRange}`);
|
|
147
|
+
console.log(` Assinatura valida: ${childVerification.signatureValid}`);
|
|
148
|
+
console.log();
|
|
149
|
+
|
|
150
|
+
// Check child compliance for a 10-year-old
|
|
151
|
+
console.log('--- Conformidade infantil (idade 10, BR) ---');
|
|
152
|
+
const childCompliance = checkChildCompliance(10, 'BR');
|
|
153
|
+
console.log(`Em conformidade: ${childCompliance.compliant}`);
|
|
154
|
+
console.log(`Idade minima de consentimento: ${childCompliance.minConsentAge}`);
|
|
155
|
+
console.log(`Maioridade: ${childCompliance.ageOfMajority}`);
|
|
156
|
+
console.log(`Consentimento parental necessario: ${childCompliance.requiresParentalConsent}`);
|
|
157
|
+
|
|
158
|
+
for (const issue of childCompliance.issues) {
|
|
159
|
+
const prefix = issue.severity === 'error' ? '[ERRO]'
|
|
160
|
+
: issue.severity === 'warning' ? '[AVISO]'
|
|
161
|
+
: '[INFO]';
|
|
162
|
+
console.log(` ${prefix} ${issue.message}`);
|
|
163
|
+
}
|
|
164
|
+
console.log();
|
|
165
|
+
|
|
166
|
+
// --- 7. Data retention ---
|
|
167
|
+
console.log('--- Retencao de dados ---');
|
|
168
|
+
const retention = getRetentionGuidance('BR');
|
|
169
|
+
console.log(`Orientacao: ${retention.guidance}`);
|
|
170
|
+
console.log(`Regulamento: ${retention.regulation}`);
|
|
171
|
+
console.log();
|
|
172
|
+
|
|
173
|
+
// --- 8. Signet IQ (Portuguese output) ---
|
|
174
|
+
console.log(`--- ${t('msg_signet_iq')} ---`);
|
|
175
|
+
const trustScore = computeTrustScore(
|
|
176
|
+
subject.publicKey,
|
|
177
|
+
[credential],
|
|
178
|
+
[],
|
|
179
|
+
Math.floor(Date.now() / 1000) - 2 * 365 * 24 * 60 * 60
|
|
180
|
+
);
|
|
181
|
+
console.log(formatLocalizedTrustScore(trustScore.score));
|
|
182
|
+
|
|
183
|
+
console.log('\n=== Exemplo de Advogado Brasileiro Concluido ===');
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
main().catch(console.error);
|