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,216 @@
|
|
|
1
|
+
// Signet Peer Connection Management
|
|
2
|
+
// ECDH-based shared secret derivation and connection lifecycle
|
|
3
|
+
|
|
4
|
+
import { secp256k1 } from '@noble/curves/secp256k1.js';
|
|
5
|
+
import { sha256 } from '@noble/hashes/sha2.js';
|
|
6
|
+
import { bytesToHex, hexToBytes, randomBytes } from '@noble/hashes/utils.js';
|
|
7
|
+
import { SignetValidationError } from './errors.js';
|
|
8
|
+
|
|
9
|
+
// ── Types ────────────────────────────────────────────────────────────────────
|
|
10
|
+
|
|
11
|
+
export interface ContactInfo {
|
|
12
|
+
name?: string;
|
|
13
|
+
mobile?: string;
|
|
14
|
+
email?: string;
|
|
15
|
+
address?: string;
|
|
16
|
+
childPubkeys?: string[];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** SECURITY NOTE: sharedSecret is stored as a plain hex string in memory.
|
|
20
|
+
* For production use, secrets should be encrypted at rest (e.g. via OS keychain
|
|
21
|
+
* or encrypted storage). JS strings cannot be zeroed from memory. */
|
|
22
|
+
export interface Connection {
|
|
23
|
+
pubkey: string; // their public key (hex)
|
|
24
|
+
sharedSecret: string; // ECDH-derived shared secret (hex, 32 bytes)
|
|
25
|
+
theirInfo: ContactInfo; // what they shared with us
|
|
26
|
+
ourInfo: ContactInfo; // what we shared with them
|
|
27
|
+
connectedAt: number; // unix timestamp
|
|
28
|
+
method: 'qr-in-person' | 'online';
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface QRPayload {
|
|
32
|
+
pubkey: string; // our public key
|
|
33
|
+
nonce: string; // random 32-byte hex for uniqueness
|
|
34
|
+
info?: ContactInfo; // optional pre-selected contact info
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// ── ECDH ─────────────────────────────────────────────────────────────────────
|
|
38
|
+
|
|
39
|
+
/** Compute an ECDH shared secret from our private key and their x-only public key.
|
|
40
|
+
* The result is the SHA-256 of the x-coordinate of the ECDH point, returned as
|
|
41
|
+
* a 32-byte hex string. The secret is symmetric: A(priv)+B(pub) === B(priv)+A(pub). */
|
|
42
|
+
export function computeSharedSecret(myPrivateKey: string, theirPublicKey: string): string {
|
|
43
|
+
// Nostr/schnorr public keys are x-only (32 bytes). To perform ECDH we need
|
|
44
|
+
// the full compressed point, so we prepend 0x02 (assume even y-coordinate,
|
|
45
|
+
// per BIP-340 convention used by Nostr).
|
|
46
|
+
const privBytes = hexToBytes(myPrivateKey);
|
|
47
|
+
const sharedPoint = secp256k1.getSharedSecret(privBytes, hexToBytes('02' + theirPublicKey));
|
|
48
|
+
privBytes.fill(0);
|
|
49
|
+
|
|
50
|
+
// sharedPoint is 33 bytes (compressed): prefix + x-coordinate. Take x bytes [1..33].
|
|
51
|
+
const xBytes = sharedPoint.slice(1, 33);
|
|
52
|
+
return bytesToHex(sha256(xBytes));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// ── QR Payload ───────────────────────────────────────────────────────────────
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Create a QR payload containing our public key and a random nonce.
|
|
59
|
+
*
|
|
60
|
+
* **SECURITY WARNING — unencrypted payload:** The returned object is serialised
|
|
61
|
+
* as cleartext JSON by `serializeQRPayload`. Any `ContactInfo` embedded in the
|
|
62
|
+
* payload (name, mobile, email, address, children's public keys) is transmitted
|
|
63
|
+
* without encryption. Only display this QR code on trusted screens in
|
|
64
|
+
* controlled environments. Do not transmit it over untrusted channels.
|
|
65
|
+
*/
|
|
66
|
+
export function createQRPayload(publicKey: string, info?: ContactInfo): QRPayload {
|
|
67
|
+
const nonce = bytesToHex(randomBytes(32));
|
|
68
|
+
const payload: QRPayload = { pubkey: publicKey, nonce };
|
|
69
|
+
if (info !== undefined) {
|
|
70
|
+
payload.info = info;
|
|
71
|
+
}
|
|
72
|
+
return payload;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Serialize a QR payload to a JSON string. */
|
|
76
|
+
export function serializeQRPayload(payload: QRPayload): string {
|
|
77
|
+
return JSON.stringify(payload);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const MAX_CONTACT_FIELD_LENGTH = 256;
|
|
81
|
+
const MAX_CHILD_PUBKEYS = 20;
|
|
82
|
+
|
|
83
|
+
/** Validate ContactInfo field sizes to prevent oversized payloads from untrusted sources. */
|
|
84
|
+
function validateContactInfo(info: unknown): void {
|
|
85
|
+
if (typeof info !== 'object' || info === null) {
|
|
86
|
+
throw new SignetValidationError('Invalid ContactInfo: must be an object');
|
|
87
|
+
}
|
|
88
|
+
const ci = info as Record<string, unknown>;
|
|
89
|
+
for (const field of ['name', 'mobile', 'email', 'address'] as const) {
|
|
90
|
+
if (ci[field] !== undefined) {
|
|
91
|
+
if (typeof ci[field] !== 'string') throw new SignetValidationError(`Invalid ContactInfo: ${field} must be a string`);
|
|
92
|
+
if ((ci[field] as string).length > MAX_CONTACT_FIELD_LENGTH) {
|
|
93
|
+
throw new SignetValidationError(`Invalid ContactInfo: ${field} exceeds ${MAX_CONTACT_FIELD_LENGTH} characters`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
if (ci.childPubkeys !== undefined) {
|
|
98
|
+
if (!Array.isArray(ci.childPubkeys)) throw new SignetValidationError('Invalid ContactInfo: childPubkeys must be an array');
|
|
99
|
+
if (ci.childPubkeys.length > MAX_CHILD_PUBKEYS) {
|
|
100
|
+
throw new SignetValidationError(`Invalid ContactInfo: childPubkeys exceeds ${MAX_CHILD_PUBKEYS} entries`);
|
|
101
|
+
}
|
|
102
|
+
for (const pk of ci.childPubkeys) {
|
|
103
|
+
if (typeof pk !== 'string' || !/^[0-9a-f]{64}$/i.test(pk)) {
|
|
104
|
+
throw new SignetValidationError('Invalid ContactInfo: childPubkeys must contain valid 64-char hex pubkeys');
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** Parse and validate a QR payload from a JSON string.
|
|
111
|
+
* Throws if the data is not valid JSON or is missing required fields. */
|
|
112
|
+
export function parseQRPayload(data: string): QRPayload {
|
|
113
|
+
let parsed: unknown;
|
|
114
|
+
try {
|
|
115
|
+
parsed = JSON.parse(data);
|
|
116
|
+
} catch {
|
|
117
|
+
throw new SignetValidationError('Invalid QR payload: malformed JSON');
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (typeof parsed !== 'object' || parsed === null) {
|
|
121
|
+
throw new SignetValidationError('Invalid QR payload: not an object');
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const obj = parsed as Record<string, unknown>;
|
|
125
|
+
|
|
126
|
+
if (typeof obj.pubkey !== 'string' || !/^[0-9a-f]{64}$/i.test(obj.pubkey)) {
|
|
127
|
+
throw new SignetValidationError('Invalid QR payload: pubkey must be a 64-character hex string');
|
|
128
|
+
}
|
|
129
|
+
if (typeof obj.nonce !== 'string' || obj.nonce.length < 32 || obj.nonce.length > 128) {
|
|
130
|
+
throw new SignetValidationError('Invalid QR payload: nonce must be 32-128 hex characters');
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Validate ContactInfo field sizes if present
|
|
134
|
+
if (obj.info !== undefined) {
|
|
135
|
+
validateContactInfo(obj.info);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return parsed as QRPayload;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// ── Connection ───────────────────────────────────────────────────────────────
|
|
142
|
+
|
|
143
|
+
/** Create a Connection from our private key and a scanned QR payload. */
|
|
144
|
+
export function createConnection(
|
|
145
|
+
myPrivateKey: string,
|
|
146
|
+
qrPayload: QRPayload,
|
|
147
|
+
ourInfo: ContactInfo,
|
|
148
|
+
): Connection {
|
|
149
|
+
const sharedSecret = computeSharedSecret(myPrivateKey, qrPayload.pubkey);
|
|
150
|
+
return {
|
|
151
|
+
pubkey: qrPayload.pubkey,
|
|
152
|
+
sharedSecret,
|
|
153
|
+
theirInfo: qrPayload.info ?? {},
|
|
154
|
+
ourInfo,
|
|
155
|
+
connectedAt: Math.floor(Date.now() / 1000),
|
|
156
|
+
method: 'qr-in-person',
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// ── ConnectionStore ──────────────────────────────────────────────────────────
|
|
161
|
+
|
|
162
|
+
/** Simple in-memory connection manager keyed by remote public key. */
|
|
163
|
+
export class ConnectionStore {
|
|
164
|
+
private connections: Map<string, Connection> = new Map();
|
|
165
|
+
|
|
166
|
+
/** Add a connection. If a connection with the same pubkey already exists it is replaced. */
|
|
167
|
+
add(connection: Connection): void {
|
|
168
|
+
this.connections.set(connection.pubkey, connection);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** Get a connection by remote public key. */
|
|
172
|
+
get(pubkey: string): Connection | undefined {
|
|
173
|
+
return this.connections.get(pubkey);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** List all connections. */
|
|
177
|
+
list(): Connection[] {
|
|
178
|
+
return Array.from(this.connections.values());
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/** Remove a connection by remote public key. Returns true if a connection was removed. */
|
|
182
|
+
remove(pubkey: string): boolean {
|
|
183
|
+
return this.connections.delete(pubkey);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** Check whether a connection for the given public key exists. */
|
|
187
|
+
has(pubkey: string): boolean {
|
|
188
|
+
return this.connections.has(pubkey);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/** Export all connections as an array (for serialization). */
|
|
192
|
+
export(): Connection[] {
|
|
193
|
+
return this.list();
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/** Import connections from an array, replacing any existing connections with the same pubkey. */
|
|
197
|
+
import(connections: Connection[]): void {
|
|
198
|
+
for (const conn of connections) {
|
|
199
|
+
if (!conn || typeof conn !== 'object') continue;
|
|
200
|
+
if (typeof conn.pubkey !== 'string' || !/^[0-9a-f]{64}$/i.test(conn.pubkey)) continue;
|
|
201
|
+
if (typeof conn.sharedSecret !== 'string' || !/^[0-9a-f]{64}$/i.test(conn.sharedSecret)) continue;
|
|
202
|
+
if (typeof conn.connectedAt !== 'number' || conn.connectedAt <= 0) continue;
|
|
203
|
+
if (typeof conn.theirInfo !== 'object' || conn.theirInfo === null) continue;
|
|
204
|
+
if (typeof conn.ourInfo !== 'object' || conn.ourInfo === null) continue;
|
|
205
|
+
if (conn.method !== 'qr-in-person' && conn.method !== 'online') continue;
|
|
206
|
+
// Validate ContactInfo field sizes to prevent oversized data from untrusted sources
|
|
207
|
+
try {
|
|
208
|
+
validateContactInfo(conn.theirInfo);
|
|
209
|
+
validateContactInfo(conn.ourInfo);
|
|
210
|
+
} catch {
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
this.connections.set(conn.pubkey, conn);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
package/src/constants.ts
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
// Signet Protocol Constants
|
|
2
|
+
import { ATTESTATION_KIND } from 'nostr-attestations';
|
|
3
|
+
|
|
4
|
+
/** Generic Verifiable Attestation kind (NIP-VA, kind 31000) */
|
|
5
|
+
export { ATTESTATION_KIND };
|
|
6
|
+
|
|
7
|
+
/** NIP-78 App-specific Data kind (existing Nostr kind) */
|
|
8
|
+
export const APP_DATA_KIND = 30078;
|
|
9
|
+
|
|
10
|
+
/** Attestation type identifiers */
|
|
11
|
+
export const ATTESTATION_TYPES = {
|
|
12
|
+
CREDENTIAL: 'credential',
|
|
13
|
+
VOUCH: 'vouch',
|
|
14
|
+
VERIFIER: 'verifier',
|
|
15
|
+
CHALLENGE: 'challenge',
|
|
16
|
+
REVOCATION: 'revocation',
|
|
17
|
+
IDENTITY_BRIDGE: 'identity-bridge',
|
|
18
|
+
DELEGATION: 'delegation',
|
|
19
|
+
} as const;
|
|
20
|
+
|
|
21
|
+
/** @deprecated — use ATTESTATION_KIND + ATTESTATION_TYPES instead */
|
|
22
|
+
export const SIGNET_KINDS = {
|
|
23
|
+
CREDENTIAL: ATTESTATION_KIND,
|
|
24
|
+
VOUCH: ATTESTATION_KIND,
|
|
25
|
+
POLICY: APP_DATA_KIND,
|
|
26
|
+
VERIFIER: ATTESTATION_KIND,
|
|
27
|
+
CHALLENGE: ATTESTATION_KIND,
|
|
28
|
+
REVOCATION: ATTESTATION_KIND,
|
|
29
|
+
IDENTITY_BRIDGE: ATTESTATION_KIND,
|
|
30
|
+
DELEGATION: ATTESTATION_KIND,
|
|
31
|
+
} as const;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* @deprecated NIP-VA labels are now auto-generated by nostr-attestations.
|
|
35
|
+
* Kept for backwards compatibility with parsers consuming legacy events.
|
|
36
|
+
*/
|
|
37
|
+
export const SIGNET_LABEL = 'signet';
|
|
38
|
+
|
|
39
|
+
/** Default number of vouches needed for Tier 2 */
|
|
40
|
+
export const DEFAULT_VOUCH_THRESHOLD = 3;
|
|
41
|
+
|
|
42
|
+
/** Default minimum tier of vouchers for Tier 2 */
|
|
43
|
+
export const DEFAULT_VOUCHER_MIN_TIER = 2;
|
|
44
|
+
|
|
45
|
+
/** Default credential expiry: 2 years in seconds */
|
|
46
|
+
export const DEFAULT_CREDENTIAL_EXPIRY_SECONDS = 2 * 365 * 24 * 60 * 60;
|
|
47
|
+
|
|
48
|
+
/** Default number of Tier 3+ confirmations to revoke a verifier */
|
|
49
|
+
export const DEFAULT_REVOCATION_THRESHOLD = 5;
|
|
50
|
+
|
|
51
|
+
/** Minimum cross-verification requirements for verifier activation */
|
|
52
|
+
export const VERIFIER_ACTIVATION = {
|
|
53
|
+
MIN_VOUCHES: 2,
|
|
54
|
+
MIN_PROFESSIONS: 2,
|
|
55
|
+
} as const;
|
|
56
|
+
|
|
57
|
+
/** Signet Score weights (default implementation, 0-200 scale) */
|
|
58
|
+
export const TRUST_WEIGHTS = {
|
|
59
|
+
PROFESSIONAL_VERIFICATION: 80,
|
|
60
|
+
IN_PERSON_VOUCH: 16,
|
|
61
|
+
ONLINE_VOUCH: 4,
|
|
62
|
+
ACCOUNT_AGE_PER_YEAR: 10,
|
|
63
|
+
ACCOUNT_AGE_MAX: 30,
|
|
64
|
+
IDENTITY_BRIDGE: 50,
|
|
65
|
+
} as const;
|
|
66
|
+
|
|
67
|
+
/** Minimum ring size for identity bridges (anonymity threshold) */
|
|
68
|
+
export const MIN_BRIDGE_RING_SIZE = 5;
|
|
69
|
+
|
|
70
|
+
/** Maximum Signet Score */
|
|
71
|
+
export const MAX_TRUST_SCORE = 200;
|
|
72
|
+
|
|
73
|
+
/** Valid entity types */
|
|
74
|
+
export const ENTITY_TYPES = [
|
|
75
|
+
'natural_person',
|
|
76
|
+
'persona',
|
|
77
|
+
'personal_agent',
|
|
78
|
+
'unlinked_personal_agent',
|
|
79
|
+
'juridical_person',
|
|
80
|
+
'juridical_persona',
|
|
81
|
+
'organised_agent',
|
|
82
|
+
'unlinked_organised_agent',
|
|
83
|
+
'unlinked_agent',
|
|
84
|
+
] as const;
|
|
85
|
+
|
|
86
|
+
/** Valid delegation owner → agent type mappings */
|
|
87
|
+
export const DELEGATION_CONSTRAINTS: Record<string, string> = {
|
|
88
|
+
natural_person: 'personal_agent',
|
|
89
|
+
persona: 'unlinked_personal_agent',
|
|
90
|
+
juridical_person: 'organised_agent',
|
|
91
|
+
juridical_persona: 'unlinked_organised_agent',
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
/** App-friendly labels for entity types */
|
|
95
|
+
export const ENTITY_LABELS: Record<string, string> = {
|
|
96
|
+
natural_person: 'Person',
|
|
97
|
+
persona: 'Alias',
|
|
98
|
+
personal_agent: 'Personal Agent',
|
|
99
|
+
unlinked_personal_agent: 'Unlinked Personal Agent',
|
|
100
|
+
juridical_person: 'Organisation',
|
|
101
|
+
juridical_persona: 'Org Alias',
|
|
102
|
+
organised_agent: 'Organised Agent',
|
|
103
|
+
unlinked_organised_agent: 'Unlinked Org Agent',
|
|
104
|
+
unlinked_agent: 'Unlinked Agent',
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
/** Domain separator for bond proof messages */
|
|
108
|
+
export const BOND_DOMAIN_SEPARATOR = 'signet:bond';
|
|
109
|
+
|
|
110
|
+
/** Default maximum age for bond proofs: 30 days in seconds */
|
|
111
|
+
export const DEFAULT_BOND_MAX_AGE_SECS = 30 * 24 * 60 * 60;
|
|
112
|
+
|
|
113
|
+
/** Valid Bitcoin address types for bond proofs */
|
|
114
|
+
export const VALID_BOND_ADDRESS_TYPES = ['p2wpkh', 'p2sh', 'p2tr', 'p2pkh'] as const;
|
|
115
|
+
|
|
116
|
+
/** Default asymmetric cryptographic algorithm (Nostr standard secp256k1).
|
|
117
|
+
* Tagged on events so future parsers can distinguish pre- and post-quantum events. */
|
|
118
|
+
export const DEFAULT_CRYPTO_ALGORITHM = 'secp256k1' as const;
|
|
119
|
+
|
|
120
|
+
/** Cold-call verification constants */
|
|
121
|
+
export const COLD_CALL_CONTEXT = 'signet:cold-call';
|
|
122
|
+
export const COLD_CALL_EPOCH_SECONDS = 30;
|
|
123
|
+
export const COLD_CALL_TOLERANCE = 1; // ±1 epoch
|
|
124
|
+
export const WELL_KNOWN_PATH = '/.well-known/signet.json';
|
|
125
|
+
export const WELL_KNOWN_MAX_SIZE = 10240; // 10 KB
|
|
126
|
+
export const WELL_KNOWN_MAX_PUBKEYS = 20;
|
|
127
|
+
export const WELL_KNOWN_MAX_CACHE_HOURS = 24;
|
|
128
|
+
export const SESSION_CODE_EXPIRY_SECONDS = 300; // 5 minutes
|
|
129
|
+
|
|
130
|
+
/** NATO phonetic alphabet for session codes */
|
|
131
|
+
export const NATO_ALPHABET = [
|
|
132
|
+
'ALFA', 'BRAVO', 'CHARLIE', 'DELTA', 'ECHO', 'FOXTROT',
|
|
133
|
+
'GOLF', 'HOTEL', 'INDIA', 'JULIET', 'KILO', 'LIMA',
|
|
134
|
+
'MIKE', 'NOVEMBER', 'OSCAR', 'PAPA', 'QUEBEC', 'ROMEO',
|
|
135
|
+
'SIERRA', 'TANGO', 'UNIFORM', 'VICTOR', 'WHISKEY',
|
|
136
|
+
'XRAY', 'YANKEE', 'ZULU',
|
|
137
|
+
] as const;
|
|
138
|
+
|
|
139
|
+
/** Signal ordering (protocol-mandated) */
|
|
140
|
+
export const SIGNAL_PRIORITY = [
|
|
141
|
+
'professional-verification',
|
|
142
|
+
'identity-bridge',
|
|
143
|
+
'in-person-vouch',
|
|
144
|
+
'online-vouch',
|
|
145
|
+
'account-age',
|
|
146
|
+
] as const;
|