signet-protocol 0.1.0
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/LICENSE +21 -0
- package/README.md +112 -0
- package/dist/anomaly.d.ts +42 -0
- package/dist/anomaly.d.ts.map +1 -0
- package/dist/anomaly.js +209 -0
- package/dist/anomaly.js.map +1 -0
- package/dist/badge.d.ts +56 -0
- package/dist/badge.d.ts.map +1 -0
- package/dist/badge.js +171 -0
- package/dist/badge.js.map +1 -0
- package/dist/bonds.d.ts +39 -0
- package/dist/bonds.d.ts.map +1 -0
- package/dist/bonds.js +149 -0
- package/dist/bonds.js.map +1 -0
- package/dist/challenges.d.ts +18 -0
- package/dist/challenges.d.ts.map +1 -0
- package/dist/challenges.js +145 -0
- package/dist/challenges.js.map +1 -0
- package/dist/cold-call.d.ts +74 -0
- package/dist/cold-call.d.ts.map +1 -0
- package/dist/cold-call.js +176 -0
- package/dist/cold-call.js.map +1 -0
- package/dist/compliance.d.ts +82 -0
- package/dist/compliance.d.ts.map +1 -0
- package/dist/compliance.js +478 -0
- package/dist/compliance.js.map +1 -0
- package/dist/connections.d.ts +63 -0
- package/dist/connections.d.ts.map +1 -0
- package/dist/connections.js +170 -0
- package/dist/connections.js.map +1 -0
- package/dist/constants.d.ts +86 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +124 -0
- package/dist/constants.js.map +1 -0
- package/dist/credentials.d.ts +190 -0
- package/dist/credentials.d.ts.map +1 -0
- package/dist/credentials.js +686 -0
- package/dist/credentials.js.map +1 -0
- package/dist/crypto.d.ts +27 -0
- package/dist/crypto.d.ts.map +1 -0
- package/dist/crypto.js +75 -0
- package/dist/crypto.js.map +1 -0
- package/dist/errors.d.ts +17 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +29 -0
- package/dist/errors.js.map +1 -0
- package/dist/i18n.d.ts +98 -0
- package/dist/i18n.d.ts.map +1 -0
- package/dist/i18n.js +1118 -0
- package/dist/i18n.js.map +1 -0
- package/dist/identity-bridge.d.ts +52 -0
- package/dist/identity-bridge.d.ts.map +1 -0
- package/dist/identity-bridge.js +228 -0
- package/dist/identity-bridge.js.map +1 -0
- package/dist/identity-tree.d.ts +47 -0
- package/dist/identity-tree.d.ts.map +1 -0
- package/dist/identity-tree.js +69 -0
- package/dist/identity-tree.js.map +1 -0
- package/dist/index.d.ts +55 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +86 -0
- package/dist/index.js.map +1 -0
- package/dist/key-derivation.d.ts +43 -0
- package/dist/key-derivation.d.ts.map +1 -0
- package/dist/key-derivation.js +212 -0
- package/dist/key-derivation.js.map +1 -0
- package/dist/lsag.d.ts +23 -0
- package/dist/lsag.d.ts.map +1 -0
- package/dist/lsag.js +35 -0
- package/dist/lsag.js.map +1 -0
- package/dist/merkle.d.ts +19 -0
- package/dist/merkle.d.ts.map +1 -0
- package/dist/merkle.js +155 -0
- package/dist/merkle.js.map +1 -0
- package/dist/policies.d.ts +22 -0
- package/dist/policies.d.ts.map +1 -0
- package/dist/policies.js +123 -0
- package/dist/policies.js.map +1 -0
- package/dist/range-proof.d.ts +6 -0
- package/dist/range-proof.d.ts.map +1 -0
- package/dist/range-proof.js +45 -0
- package/dist/range-proof.js.map +1 -0
- package/dist/relay.d.ts +106 -0
- package/dist/relay.d.ts.map +1 -0
- package/dist/relay.js +336 -0
- package/dist/relay.js.map +1 -0
- package/dist/ring-signature.d.ts +35 -0
- package/dist/ring-signature.d.ts.map +1 -0
- package/dist/ring-signature.js +56 -0
- package/dist/ring-signature.js.map +1 -0
- package/dist/shamir.d.ts +55 -0
- package/dist/shamir.d.ts.map +1 -0
- package/dist/shamir.js +253 -0
- package/dist/shamir.js.map +1 -0
- package/dist/signet-words.d.ts +42 -0
- package/dist/signet-words.d.ts.map +1 -0
- package/dist/signet-words.js +82 -0
- package/dist/signet-words.js.map +1 -0
- package/dist/store.d.ts +65 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +290 -0
- package/dist/store.js.map +1 -0
- package/dist/trust-score.d.ts +9 -0
- package/dist/trust-score.d.ts.map +1 -0
- package/dist/trust-score.js +186 -0
- package/dist/trust-score.js.map +1 -0
- package/dist/types.d.ts +358 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +15 -0
- package/dist/types.js.map +1 -0
- package/dist/utils.d.ts +11 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +21 -0
- package/dist/utils.js.map +1 -0
- package/dist/validation.d.ts +33 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +312 -0
- package/dist/validation.js.map +1 -0
- package/dist/verifiers.d.ts +18 -0
- package/dist/verifiers.d.ts.map +1 -0
- package/dist/verifiers.js +118 -0
- package/dist/verifiers.js.map +1 -0
- package/dist/vouches.d.ts +14 -0
- package/dist/vouches.d.ts.map +1 -0
- package/dist/vouches.js +103 -0
- package/dist/vouches.js.map +1 -0
- package/package.json +76 -0
- package/src/anomaly.ts +307 -0
- package/src/badge.ts +208 -0
- package/src/bonds.ts +203 -0
- package/src/challenges.ts +187 -0
- package/src/cold-call.ts +238 -0
- package/src/compliance.ts +612 -0
- package/src/connections.ts +216 -0
- package/src/constants.ts +146 -0
- package/src/credentials.ts +908 -0
- package/src/crypto.ts +85 -0
- package/src/errors.ts +31 -0
- package/src/i18n.ts +1347 -0
- package/src/identity-bridge.ts +262 -0
- package/src/identity-tree.ts +90 -0
- package/src/index.ts +452 -0
- package/src/lsag.ts +53 -0
- package/src/merkle.ts +176 -0
- package/src/policies.ts +154 -0
- package/src/range-proof.ts +66 -0
- package/src/relay.ts +433 -0
- package/src/ring-signature.ts +76 -0
- package/src/signet-words.ts +122 -0
- package/src/store.ts +336 -0
- package/src/trust-score.ts +208 -0
- package/src/types.ts +482 -0
- package/src/utils.ts +20 -0
- package/src/validation.ts +391 -0
- package/src/verifiers.ts +156 -0
- package/src/vouches.ts +141 -0
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
// Signet Score Computation
|
|
2
|
+
// Continuous 0-200 Signet Score from weighted signals
|
|
3
|
+
|
|
4
|
+
import { TRUST_WEIGHTS, MAX_TRUST_SCORE, ATTESTATION_KIND, ATTESTATION_TYPES } from './constants.js';
|
|
5
|
+
import { getTagValue } from './validation.js';
|
|
6
|
+
import type {
|
|
7
|
+
NostrEvent,
|
|
8
|
+
TrustSignal,
|
|
9
|
+
TrustScoreBreakdown,
|
|
10
|
+
SignetTier,
|
|
11
|
+
} from './types.js';
|
|
12
|
+
|
|
13
|
+
/** Compute Signet Score for a subject from their credentials, vouches, and bridges */
|
|
14
|
+
export function computeTrustScore(
|
|
15
|
+
subjectPubkey: string,
|
|
16
|
+
credentials: NostrEvent[],
|
|
17
|
+
vouches: NostrEvent[],
|
|
18
|
+
accountCreatedAt?: number,
|
|
19
|
+
bridges?: NostrEvent[]
|
|
20
|
+
): TrustScoreBreakdown {
|
|
21
|
+
const signals: TrustSignal[] = [];
|
|
22
|
+
let rawScore = 0;
|
|
23
|
+
let highestTier: SignetTier = 1;
|
|
24
|
+
let professionalVerifications = 0;
|
|
25
|
+
let inPersonVouches = 0;
|
|
26
|
+
let onlineVouches = 0;
|
|
27
|
+
|
|
28
|
+
// Process credentials
|
|
29
|
+
const now = Math.floor(Date.now() / 1000);
|
|
30
|
+
for (const cred of credentials) {
|
|
31
|
+
if (cred.kind !== ATTESTATION_KIND) continue;
|
|
32
|
+
if (getTagValue(cred, 'type') !== ATTESTATION_TYPES.CREDENTIAL) continue;
|
|
33
|
+
const dTag = getTagValue(cred, 'd') || '';
|
|
34
|
+
const pTag = getTagValue(cred, 'p');
|
|
35
|
+
let subject: string;
|
|
36
|
+
if (dTag.startsWith('assertion:') && pTag) {
|
|
37
|
+
subject = pTag;
|
|
38
|
+
} else {
|
|
39
|
+
subject = dTag.startsWith('credential:') ? dTag.slice('credential:'.length) : dTag;
|
|
40
|
+
}
|
|
41
|
+
if (subject !== subjectPubkey) continue;
|
|
42
|
+
|
|
43
|
+
// Skip expired credentials — NaN must be treated as expired (not perpetually valid)
|
|
44
|
+
const expires = getTagValue(cred, 'expiration');
|
|
45
|
+
if (expires) {
|
|
46
|
+
const exp = parseInt(expires, 10);
|
|
47
|
+
if (isNaN(exp) || exp < now) continue;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const rawTier = parseInt(getTagValue(cred, 'tier') || '1', 10);
|
|
51
|
+
const tier = (!isNaN(rawTier) && rawTier >= 1 && rawTier <= 4 ? rawTier : 1) as SignetTier;
|
|
52
|
+
if (tier > highestTier) highestTier = tier;
|
|
53
|
+
|
|
54
|
+
const verificationType = getTagValue(cred, 'verification-type');
|
|
55
|
+
if (verificationType === 'professional') {
|
|
56
|
+
professionalVerifications++;
|
|
57
|
+
const weight = TRUST_WEIGHTS.PROFESSIONAL_VERIFICATION;
|
|
58
|
+
rawScore += weight;
|
|
59
|
+
signals.push({
|
|
60
|
+
type: 'professional-verification',
|
|
61
|
+
weight,
|
|
62
|
+
source: cred.pubkey,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Process vouches
|
|
68
|
+
const vouchersSeen = new Set<string>();
|
|
69
|
+
for (const vouch of vouches) {
|
|
70
|
+
if (vouch.kind !== ATTESTATION_KIND) continue;
|
|
71
|
+
if (getTagValue(vouch, 'type') !== ATTESTATION_TYPES.VOUCH) continue;
|
|
72
|
+
const dTag = getTagValue(vouch, 'd') || '';
|
|
73
|
+
const subject = dTag.startsWith('vouch:') ? dTag.slice('vouch:'.length) : dTag;
|
|
74
|
+
if (subject !== subjectPubkey) continue;
|
|
75
|
+
|
|
76
|
+
// One vouch per voucher
|
|
77
|
+
if (vouchersSeen.has(vouch.pubkey)) continue;
|
|
78
|
+
vouchersSeen.add(vouch.pubkey);
|
|
79
|
+
|
|
80
|
+
const method = getTagValue(vouch, 'method');
|
|
81
|
+
const rawVoucherScore = parseInt(getTagValue(vouch, 'voucher-score') || '50', 10);
|
|
82
|
+
const voucherScore = isNaN(rawVoucherScore) ? 50 : Math.max(0, Math.min(rawVoucherScore, MAX_TRUST_SCORE));
|
|
83
|
+
const scoreMultiplier = voucherScore / MAX_TRUST_SCORE;
|
|
84
|
+
|
|
85
|
+
if (method === 'in-person') {
|
|
86
|
+
inPersonVouches++;
|
|
87
|
+
const weight = TRUST_WEIGHTS.IN_PERSON_VOUCH * scoreMultiplier;
|
|
88
|
+
rawScore += weight;
|
|
89
|
+
signals.push({
|
|
90
|
+
type: 'in-person-vouch',
|
|
91
|
+
weight,
|
|
92
|
+
source: vouch.pubkey,
|
|
93
|
+
score: voucherScore,
|
|
94
|
+
});
|
|
95
|
+
} else {
|
|
96
|
+
onlineVouches++;
|
|
97
|
+
const weight = TRUST_WEIGHTS.ONLINE_VOUCH * scoreMultiplier;
|
|
98
|
+
rawScore += weight;
|
|
99
|
+
signals.push({
|
|
100
|
+
type: 'online-vouch',
|
|
101
|
+
weight,
|
|
102
|
+
source: vouch.pubkey,
|
|
103
|
+
score: voucherScore,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Process identity bridges
|
|
109
|
+
if (bridges) {
|
|
110
|
+
for (const bridge of bridges) {
|
|
111
|
+
if (bridge.kind !== ATTESTATION_KIND) continue;
|
|
112
|
+
if (getTagValue(bridge, 'type') !== ATTESTATION_TYPES.IDENTITY_BRIDGE) continue;
|
|
113
|
+
if (bridge.pubkey !== subjectPubkey) continue;
|
|
114
|
+
|
|
115
|
+
const rawRingMinTier = parseInt(getTagValue(bridge, 'ring-min-tier') || '1', 10);
|
|
116
|
+
const ringMinTier = (!isNaN(rawRingMinTier) && rawRingMinTier >= 1 && rawRingMinTier <= 4 ? rawRingMinTier : 1) as SignetTier;
|
|
117
|
+
const weight = TRUST_WEIGHTS.IDENTITY_BRIDGE * (ringMinTier / 4);
|
|
118
|
+
rawScore += weight;
|
|
119
|
+
signals.push({
|
|
120
|
+
type: 'identity-bridge',
|
|
121
|
+
weight,
|
|
122
|
+
source: bridge.pubkey,
|
|
123
|
+
});
|
|
124
|
+
// Only count one bridge per account
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Account age
|
|
130
|
+
let accountAgeDays = 0;
|
|
131
|
+
if (accountCreatedAt) {
|
|
132
|
+
accountAgeDays = Math.max(0, Math.floor((now - accountCreatedAt) / (24 * 60 * 60)));
|
|
133
|
+
const years = accountAgeDays / 365;
|
|
134
|
+
const ageWeight = Math.min(
|
|
135
|
+
years * TRUST_WEIGHTS.ACCOUNT_AGE_PER_YEAR,
|
|
136
|
+
TRUST_WEIGHTS.ACCOUNT_AGE_MAX
|
|
137
|
+
);
|
|
138
|
+
rawScore += ageWeight;
|
|
139
|
+
signals.push({
|
|
140
|
+
type: 'account-age',
|
|
141
|
+
weight: ageWeight,
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Cap at MAX_TRUST_SCORE
|
|
146
|
+
const score = Math.min(Math.round(rawScore), MAX_TRUST_SCORE);
|
|
147
|
+
|
|
148
|
+
return {
|
|
149
|
+
score,
|
|
150
|
+
tier: highestTier,
|
|
151
|
+
professionalVerifications,
|
|
152
|
+
inPersonVouches,
|
|
153
|
+
onlineVouches,
|
|
154
|
+
accountAgeDays,
|
|
155
|
+
signals,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/** Get a human-readable Signet Score display */
|
|
160
|
+
export function formatTrustDisplay(breakdown: TrustScoreBreakdown): string {
|
|
161
|
+
const checkmarks = breakdown.tier >= 2 ? '✓'.repeat(breakdown.tier - 1) : '';
|
|
162
|
+
const tierLabel = ['', 'Self-declared', 'Web-of-trust', 'Verified', 'Verified (Child Safety)'][breakdown.tier];
|
|
163
|
+
|
|
164
|
+
const lines = [
|
|
165
|
+
`${checkmarks} Tier ${breakdown.tier} (${tierLabel})`,
|
|
166
|
+
`Signet Score: ${breakdown.score}`,
|
|
167
|
+
];
|
|
168
|
+
|
|
169
|
+
if (breakdown.professionalVerifications > 0) {
|
|
170
|
+
lines.push(` Prof verified: ${breakdown.professionalVerifications}`);
|
|
171
|
+
}
|
|
172
|
+
if (breakdown.inPersonVouches > 0) {
|
|
173
|
+
lines.push(` In-person vouches: ${breakdown.inPersonVouches}`);
|
|
174
|
+
}
|
|
175
|
+
if (breakdown.onlineVouches > 0) {
|
|
176
|
+
lines.push(` Online vouches: ${breakdown.onlineVouches}`);
|
|
177
|
+
}
|
|
178
|
+
if (breakdown.accountAgeDays > 0) {
|
|
179
|
+
const years = (breakdown.accountAgeDays / 365).toFixed(1);
|
|
180
|
+
lines.push(` Account age: ${years} years`);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return lines.join('\n');
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** Verify the signal ordering invariant:
|
|
187
|
+
* professional verification > identity bridge > in-person vouch > online vouch > account age */
|
|
188
|
+
export function verifySignalOrdering(signals: TrustSignal[]): boolean {
|
|
189
|
+
const maxWeight = (type: TrustSignal['type']): number => {
|
|
190
|
+
const weights = signals.filter((s) => s.type === type).map((s) => s.weight);
|
|
191
|
+
return weights.length > 0 ? Math.max(...weights) : 0;
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
const profMax = maxWeight('professional-verification');
|
|
195
|
+
const bridgeMax = maxWeight('identity-bridge');
|
|
196
|
+
const inPersonMax = maxWeight('in-person-vouch');
|
|
197
|
+
|
|
198
|
+
// Professional verification must exceed identity bridge
|
|
199
|
+
if (profMax > 0 && bridgeMax > 0 && profMax <= bridgeMax) return false;
|
|
200
|
+
|
|
201
|
+
// Identity bridge must exceed in-person vouch
|
|
202
|
+
if (bridgeMax > 0 && inPersonMax > 0 && bridgeMax <= inPersonMax) return false;
|
|
203
|
+
|
|
204
|
+
// Professional verification must exceed in-person vouch
|
|
205
|
+
if (profMax > 0 && inPersonMax > 0 && profMax <= inPersonMax) return false;
|
|
206
|
+
|
|
207
|
+
return true;
|
|
208
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,482 @@
|
|
|
1
|
+
// Signet Protocol Types
|
|
2
|
+
// All TypeScript interfaces for the 11 Nostr event kinds (8 core + 3 voting extension) and supporting structures
|
|
3
|
+
|
|
4
|
+
/** Asymmetric cryptographic algorithm for event signing and key agreement.
|
|
5
|
+
* 'secp256k1' is the current Nostr standard. Additional algorithms
|
|
6
|
+
* (e.g. post-quantum lattice-based schemes) may be added in future versions. */
|
|
7
|
+
export type CryptoAlgorithm = 'secp256k1' | (string & {});
|
|
8
|
+
|
|
9
|
+
/** Verification tier levels */
|
|
10
|
+
export type SignetTier = 1 | 2 | 3 | 4;
|
|
11
|
+
|
|
12
|
+
/** How the verification was performed */
|
|
13
|
+
export type VerificationType = 'self' | 'peer' | 'professional';
|
|
14
|
+
|
|
15
|
+
/** Scope of verification */
|
|
16
|
+
export type VerificationScope = 'adult' | 'adult+child';
|
|
17
|
+
|
|
18
|
+
/** Method of verification */
|
|
19
|
+
export type VerificationMethod =
|
|
20
|
+
| 'self-declaration'
|
|
21
|
+
| 'in-person'
|
|
22
|
+
| 'online'
|
|
23
|
+
| 'in-person-id';
|
|
24
|
+
|
|
25
|
+
/** Vouch method */
|
|
26
|
+
export type VouchMethod = 'in-person' | 'online';
|
|
27
|
+
|
|
28
|
+
/** Policy enforcement level */
|
|
29
|
+
export type EnforcementLevel = 'client' | 'relay' | 'both';
|
|
30
|
+
|
|
31
|
+
/** Challenge reasons */
|
|
32
|
+
export type ChallengeReason =
|
|
33
|
+
| 'anomalous-volume'
|
|
34
|
+
| 'registry-mismatch'
|
|
35
|
+
| 'fraudulent-attestation'
|
|
36
|
+
| 'licence-revoked'
|
|
37
|
+
| 'other';
|
|
38
|
+
|
|
39
|
+
/** Bond action on revocation */
|
|
40
|
+
export type BondAction = 'slashed' | 'returned' | 'held';
|
|
41
|
+
|
|
42
|
+
/** Revocation scope */
|
|
43
|
+
export type RevocationScope = 'full' | 'partial';
|
|
44
|
+
|
|
45
|
+
// --- Base Nostr Event ---
|
|
46
|
+
|
|
47
|
+
export interface UnsignedEvent {
|
|
48
|
+
kind: number;
|
|
49
|
+
pubkey: string;
|
|
50
|
+
created_at: number;
|
|
51
|
+
tags: string[][];
|
|
52
|
+
content: string;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface NostrEvent extends UnsignedEvent {
|
|
56
|
+
id: string;
|
|
57
|
+
sig: string;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// --- Verification Credential (type: credential) ---
|
|
61
|
+
|
|
62
|
+
export interface CredentialParams {
|
|
63
|
+
subjectPubkey: string;
|
|
64
|
+
tier: SignetTier;
|
|
65
|
+
type: VerificationType;
|
|
66
|
+
scope: VerificationScope;
|
|
67
|
+
method: VerificationMethod;
|
|
68
|
+
expiresAt: number;
|
|
69
|
+
profession?: string;
|
|
70
|
+
jurisdiction?: string;
|
|
71
|
+
ageRange?: string; // e.g. "8-12", "18+"
|
|
72
|
+
content?: string; // ZKP proof blob
|
|
73
|
+
entityType?: EntityType;
|
|
74
|
+
nullifier?: string;
|
|
75
|
+
merkleRoot?: string;
|
|
76
|
+
guardianPubkeys?: string[];
|
|
77
|
+
supersedes?: string; // event ID of superseded credential
|
|
78
|
+
occurredAt?: number; // unix timestamp for when verification occurred (distinct from publication time)
|
|
79
|
+
assertionEventId?: string; // Tier 1 self-declaration event ID (REQUIRED for Tier 2-4, assertion-first pattern)
|
|
80
|
+
assertionRelay?: string; // relay hint for the assertion event
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// --- Vouch Attestation (type: vouch) ---
|
|
84
|
+
|
|
85
|
+
export interface VouchParams {
|
|
86
|
+
subjectPubkey: string;
|
|
87
|
+
method: VouchMethod;
|
|
88
|
+
context?: string;
|
|
89
|
+
voucherTier: SignetTier;
|
|
90
|
+
voucherScore: number;
|
|
91
|
+
occurredAt?: number;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// --- Community Verification Policy (NIP-78) ---
|
|
95
|
+
|
|
96
|
+
export interface PolicyParams {
|
|
97
|
+
communityId: string;
|
|
98
|
+
adultMinTier: SignetTier;
|
|
99
|
+
childMinTier: SignetTier;
|
|
100
|
+
enforcement: EnforcementLevel;
|
|
101
|
+
minScore?: number;
|
|
102
|
+
modMinTier?: SignetTier;
|
|
103
|
+
verifierBond?: number;
|
|
104
|
+
revocationThreshold?: number;
|
|
105
|
+
description?: string;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export interface PolicyCheckResult {
|
|
109
|
+
allowed: boolean;
|
|
110
|
+
reason?: string;
|
|
111
|
+
requiredTier: SignetTier;
|
|
112
|
+
actualTier: SignetTier;
|
|
113
|
+
requiredScore?: number;
|
|
114
|
+
actualScore?: number;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// --- Proof-of-Reserve Bond ---
|
|
118
|
+
|
|
119
|
+
/** Bitcoin address type for bond proofs */
|
|
120
|
+
export type BitcoinAddressType = 'p2wpkh' | 'p2sh' | 'p2tr' | 'p2pkh';
|
|
121
|
+
|
|
122
|
+
/** A proof-of-reserve bond attestation */
|
|
123
|
+
export interface BondProof {
|
|
124
|
+
address: string;
|
|
125
|
+
addressType: BitcoinAddressType;
|
|
126
|
+
amountSats: number;
|
|
127
|
+
timestamp: number;
|
|
128
|
+
message: string;
|
|
129
|
+
signature: string;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export type BondStatus = 'valid' | 'stale' | 'invalid' | 'unverified';
|
|
133
|
+
|
|
134
|
+
export interface BondVerificationResult {
|
|
135
|
+
status: BondStatus;
|
|
136
|
+
signatureValid: boolean | null;
|
|
137
|
+
fresh: boolean;
|
|
138
|
+
ageSecs: number;
|
|
139
|
+
meetsThreshold: boolean | null;
|
|
140
|
+
errors: string[];
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export type BIP322Verifier = (
|
|
144
|
+
address: string,
|
|
145
|
+
message: string,
|
|
146
|
+
signature: string,
|
|
147
|
+
) => boolean | Promise<boolean>;
|
|
148
|
+
|
|
149
|
+
// --- Verifier Credential (type: verifier) ---
|
|
150
|
+
|
|
151
|
+
export interface VerifierParams {
|
|
152
|
+
profession: string;
|
|
153
|
+
jurisdiction: string;
|
|
154
|
+
licenceHash: string;
|
|
155
|
+
professionalBody: string;
|
|
156
|
+
statement?: string;
|
|
157
|
+
bondProof?: BondProof;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// --- Verifier Challenge (type: challenge) ---
|
|
161
|
+
|
|
162
|
+
export interface ChallengeParams {
|
|
163
|
+
verifierPubkey: string;
|
|
164
|
+
reason: ChallengeReason;
|
|
165
|
+
evidenceType: string;
|
|
166
|
+
reporterTier: SignetTier;
|
|
167
|
+
evidence: string;
|
|
168
|
+
occurredAt?: number;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// --- Verifier Revocation (type: revocation) ---
|
|
172
|
+
|
|
173
|
+
export interface RevocationParams {
|
|
174
|
+
verifierPubkey: string;
|
|
175
|
+
challengeEventId: string;
|
|
176
|
+
confirmations: number;
|
|
177
|
+
bondAction: BondAction;
|
|
178
|
+
scope: RevocationScope;
|
|
179
|
+
effectiveAt: number;
|
|
180
|
+
summary: string;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// --- Signet Score ---
|
|
184
|
+
|
|
185
|
+
export interface TrustSignal {
|
|
186
|
+
type: 'professional-verification' | 'in-person-vouch' | 'online-vouch' | 'account-age' | 'identity-bridge';
|
|
187
|
+
weight: number;
|
|
188
|
+
source?: string; // pubkey of signal source
|
|
189
|
+
score?: number; // source's own score (for vouch multiplier)
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export interface TrustScoreBreakdown {
|
|
193
|
+
score: number; // 0-200 (Signet Score)
|
|
194
|
+
tier: SignetTier;
|
|
195
|
+
professionalVerifications: number;
|
|
196
|
+
inPersonVouches: number;
|
|
197
|
+
onlineVouches: number;
|
|
198
|
+
accountAgeDays: number;
|
|
199
|
+
signals: TrustSignal[];
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// --- Merkle Selective Disclosure ---
|
|
203
|
+
|
|
204
|
+
export interface MerkleProof {
|
|
205
|
+
leaf: string;
|
|
206
|
+
index: number;
|
|
207
|
+
siblings: string[];
|
|
208
|
+
root: string;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export interface SelectiveDisclosure {
|
|
212
|
+
revealedAttributes: Record<string, string>;
|
|
213
|
+
proofs: MerkleProof[];
|
|
214
|
+
merkleRoot: string;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// --- Parsed event helpers ---
|
|
218
|
+
|
|
219
|
+
export interface ParsedCredential {
|
|
220
|
+
subjectPubkey: string;
|
|
221
|
+
tier: SignetTier;
|
|
222
|
+
type: VerificationType;
|
|
223
|
+
scope: VerificationScope;
|
|
224
|
+
method: VerificationMethod;
|
|
225
|
+
profession?: string;
|
|
226
|
+
jurisdiction?: string;
|
|
227
|
+
ageRange?: string;
|
|
228
|
+
expiresAt?: number;
|
|
229
|
+
entityType?: EntityType;
|
|
230
|
+
nullifier?: string;
|
|
231
|
+
merkleRoot?: string;
|
|
232
|
+
guardianPubkeys?: string[];
|
|
233
|
+
supersedes?: string;
|
|
234
|
+
supersededBy?: string;
|
|
235
|
+
occurredAt?: number;
|
|
236
|
+
algorithm: CryptoAlgorithm;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export interface ParsedVouch {
|
|
240
|
+
subjectPubkey: string;
|
|
241
|
+
method: VouchMethod;
|
|
242
|
+
context?: string;
|
|
243
|
+
voucherTier: SignetTier;
|
|
244
|
+
voucherScore: number;
|
|
245
|
+
algorithm: CryptoAlgorithm;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
export interface ParsedPolicy {
|
|
249
|
+
communityId: string;
|
|
250
|
+
adultMinTier: SignetTier;
|
|
251
|
+
childMinTier: SignetTier;
|
|
252
|
+
enforcement: EnforcementLevel;
|
|
253
|
+
minScore?: number;
|
|
254
|
+
modMinTier?: SignetTier;
|
|
255
|
+
verifierBond?: number;
|
|
256
|
+
revocationThreshold?: number;
|
|
257
|
+
algorithm: CryptoAlgorithm;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
export interface ParsedVerifier {
|
|
261
|
+
profession: string;
|
|
262
|
+
jurisdiction: string;
|
|
263
|
+
licenceHash: string;
|
|
264
|
+
professionalBody: string;
|
|
265
|
+
algorithm: CryptoAlgorithm;
|
|
266
|
+
bondProof?: BondProof;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
export interface ParsedChallenge {
|
|
270
|
+
verifierPubkey: string;
|
|
271
|
+
reason: ChallengeReason;
|
|
272
|
+
evidenceType: string;
|
|
273
|
+
reporterTier: SignetTier;
|
|
274
|
+
algorithm: CryptoAlgorithm;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
export interface ParsedRevocation {
|
|
278
|
+
verifierPubkey: string;
|
|
279
|
+
challengeEventId: string;
|
|
280
|
+
confirmations: number;
|
|
281
|
+
bondAction: BondAction;
|
|
282
|
+
scope: RevocationScope;
|
|
283
|
+
effectiveAt: number;
|
|
284
|
+
algorithm: CryptoAlgorithm;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// --- Entity Type Classification ---
|
|
288
|
+
|
|
289
|
+
/** Entity type classification (§17 of spec) */
|
|
290
|
+
export type EntityType =
|
|
291
|
+
| 'natural_person'
|
|
292
|
+
| 'persona'
|
|
293
|
+
| 'personal_agent'
|
|
294
|
+
| 'unlinked_personal_agent'
|
|
295
|
+
| 'juridical_person'
|
|
296
|
+
| 'juridical_persona'
|
|
297
|
+
| 'organised_agent'
|
|
298
|
+
| 'unlinked_organised_agent'
|
|
299
|
+
| 'unlinked_agent';
|
|
300
|
+
|
|
301
|
+
/** Developer-friendly entity type labels */
|
|
302
|
+
export type SimpleEntityType = 'person' | 'persona' | 'organisation' | 'agent';
|
|
303
|
+
|
|
304
|
+
/** Maps full entity types to simplified labels */
|
|
305
|
+
export const ENTITY_DISPLAY_LABELS: Record<EntityType, SimpleEntityType> = {
|
|
306
|
+
natural_person: 'person',
|
|
307
|
+
persona: 'persona',
|
|
308
|
+
juridical_person: 'organisation',
|
|
309
|
+
juridical_persona: 'organisation',
|
|
310
|
+
personal_agent: 'agent',
|
|
311
|
+
organised_agent: 'agent',
|
|
312
|
+
unlinked_agent: 'agent',
|
|
313
|
+
unlinked_personal_agent: 'agent',
|
|
314
|
+
unlinked_organised_agent: 'agent',
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
/** Dynamic mode signaling for teleoperated/autonomous entities (§17.9) */
|
|
318
|
+
export type EntityMode =
|
|
319
|
+
| 'teleoperated'
|
|
320
|
+
| 'autonomous'
|
|
321
|
+
| 'assisted';
|
|
322
|
+
|
|
323
|
+
// --- Agent Delegation ---
|
|
324
|
+
|
|
325
|
+
export interface ParsedDelegation {
|
|
326
|
+
agentPubkey: string;
|
|
327
|
+
entityType: 'personal_agent' | 'unlinked_personal_agent' | 'organised_agent' | 'unlinked_organised_agent';
|
|
328
|
+
ownerPubkey: string;
|
|
329
|
+
expiresAt?: number;
|
|
330
|
+
algorithm: CryptoAlgorithm;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// --- Voting Extension (spec/voting.md) ---
|
|
334
|
+
|
|
335
|
+
/** Election scale */
|
|
336
|
+
export type ElectionScale = 'organisational' | 'community' | 'national';
|
|
337
|
+
|
|
338
|
+
/** Re-vote policy */
|
|
339
|
+
export type ReVotePolicy = 'allowed' | 'denied';
|
|
340
|
+
|
|
341
|
+
/** Kind 30482: Election Definition */
|
|
342
|
+
export interface ElectionParams {
|
|
343
|
+
electionId: string;
|
|
344
|
+
title: string;
|
|
345
|
+
description?: string;
|
|
346
|
+
options: string[];
|
|
347
|
+
scale: ElectionScale;
|
|
348
|
+
eligibleEntityTypes: EntityType[];
|
|
349
|
+
eligibleMinTier: SignetTier;
|
|
350
|
+
eligibleCommunity?: string;
|
|
351
|
+
opens: number;
|
|
352
|
+
closes: number;
|
|
353
|
+
reVote: ReVotePolicy;
|
|
354
|
+
tallyPubkeys: string[];
|
|
355
|
+
tallyThreshold?: [m: number, n: number];
|
|
356
|
+
ringSize?: number;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
export interface ParsedElection {
|
|
360
|
+
electionId: string;
|
|
361
|
+
title: string;
|
|
362
|
+
description?: string;
|
|
363
|
+
options: string[];
|
|
364
|
+
scale: ElectionScale;
|
|
365
|
+
eligibleEntityTypes: EntityType[];
|
|
366
|
+
eligibleMinTier: SignetTier;
|
|
367
|
+
eligibleCommunity?: string;
|
|
368
|
+
opens: number;
|
|
369
|
+
closes: number;
|
|
370
|
+
reVote: ReVotePolicy;
|
|
371
|
+
tallyPubkeys: string[];
|
|
372
|
+
tallyThreshold?: [m: number, n: number];
|
|
373
|
+
ringSize?: number;
|
|
374
|
+
authorityPubkey: string;
|
|
375
|
+
algorithm: CryptoAlgorithm;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/** Kind 30483: Ballot */
|
|
379
|
+
export interface BallotParams {
|
|
380
|
+
electionId: string;
|
|
381
|
+
electionEventId: string;
|
|
382
|
+
keyImage: string;
|
|
383
|
+
ringSig: string;
|
|
384
|
+
encryptedVote: string;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
export interface ParsedBallot {
|
|
388
|
+
electionId: string;
|
|
389
|
+
electionEventId: string;
|
|
390
|
+
keyImage: string;
|
|
391
|
+
ringSig: string;
|
|
392
|
+
encryptedVote: string;
|
|
393
|
+
ephemeralPubkey: string;
|
|
394
|
+
timestamp: number;
|
|
395
|
+
algorithm: CryptoAlgorithm;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/** Kind 30484: Election Result */
|
|
399
|
+
export interface ElectionResultParams {
|
|
400
|
+
electionId: string;
|
|
401
|
+
electionEventId: string;
|
|
402
|
+
results: Array<{ option: string; count: number }>;
|
|
403
|
+
totalBallots: number;
|
|
404
|
+
totalEligible: number;
|
|
405
|
+
totalInvalid?: number;
|
|
406
|
+
tallyProof?: string;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
export interface ParsedElectionResult {
|
|
410
|
+
electionId: string;
|
|
411
|
+
electionEventId: string;
|
|
412
|
+
results: Array<{ option: string; count: number }>;
|
|
413
|
+
totalBallots: number;
|
|
414
|
+
totalEligible: number;
|
|
415
|
+
totalInvalid: number;
|
|
416
|
+
tallyProof?: string;
|
|
417
|
+
tallierPubkey: string;
|
|
418
|
+
algorithm: CryptoAlgorithm;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// --- Two-Credential Ceremony ---
|
|
422
|
+
|
|
423
|
+
export interface TwoCredentialResult {
|
|
424
|
+
naturalPerson: NostrEvent;
|
|
425
|
+
persona: NostrEvent;
|
|
426
|
+
merkleLeaves: Record<string, string>;
|
|
427
|
+
merkleProofs: MerkleProof[];
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
export interface CredentialChain {
|
|
431
|
+
current: NostrEvent;
|
|
432
|
+
history: NostrEvent[];
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// --- Guardian Delegation ---
|
|
436
|
+
|
|
437
|
+
export interface GuardianDelegationParams {
|
|
438
|
+
childPubkey: string;
|
|
439
|
+
delegatePubkey: string;
|
|
440
|
+
scope: GuardianDelegationScope;
|
|
441
|
+
expiresAt?: number;
|
|
442
|
+
occurredAt?: number;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
export type GuardianDelegationScope =
|
|
446
|
+
| 'full'
|
|
447
|
+
| 'activity-approval'
|
|
448
|
+
| 'content-management'
|
|
449
|
+
| 'contact-approval';
|
|
450
|
+
|
|
451
|
+
// --- Cold-Call Verification ---
|
|
452
|
+
|
|
453
|
+
/** A single institutional verification pubkey */
|
|
454
|
+
export interface InstitutionPubkey {
|
|
455
|
+
id: string;
|
|
456
|
+
pubkey: string; // 64-char hex, secp256k1 x-only
|
|
457
|
+
label: string;
|
|
458
|
+
created: string; // ISO 8601
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
/** .well-known/signet.json response */
|
|
462
|
+
export interface InstitutionKeys {
|
|
463
|
+
version: number;
|
|
464
|
+
name: string;
|
|
465
|
+
pubkeys: InstitutionPubkey[];
|
|
466
|
+
relay?: string;
|
|
467
|
+
policy?: {
|
|
468
|
+
rotation?: string;
|
|
469
|
+
contact?: string;
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// --- Identity Bridge (type: identity-bridge) ---
|
|
474
|
+
|
|
475
|
+
export interface ParsedIdentityBridge {
|
|
476
|
+
anonPubkey: string;
|
|
477
|
+
ringMinTier: SignetTier;
|
|
478
|
+
ringSize: number;
|
|
479
|
+
ring: string[];
|
|
480
|
+
timestamp: number;
|
|
481
|
+
algorithm: CryptoAlgorithm;
|
|
482
|
+
}
|
package/src/utils.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/** Zero out a Uint8Array buffer (best-effort secure wipe). */
|
|
2
|
+
export function zeroBytes(buf: Uint8Array): void {
|
|
3
|
+
buf.fill(0);
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Constant-time comparison of two equal-length Uint8Arrays.
|
|
8
|
+
* Always compares all bytes; does not short-circuit on content.
|
|
9
|
+
*
|
|
10
|
+
* NOTE: The length check IS an early return (not constant-time w.r.t. length),
|
|
11
|
+
* but all callers compare fixed-size buffers (32-byte scalars), so this is safe.
|
|
12
|
+
*/
|
|
13
|
+
export function constantTimeEqual(a: Uint8Array, b: Uint8Array): boolean {
|
|
14
|
+
if (a.length !== b.length) return false;
|
|
15
|
+
let diff = 0;
|
|
16
|
+
for (let i = 0; i < a.length; i++) {
|
|
17
|
+
diff |= a[i] ^ b[i];
|
|
18
|
+
}
|
|
19
|
+
return diff === 0;
|
|
20
|
+
}
|