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.
Files changed (156) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +112 -0
  3. package/dist/anomaly.d.ts +42 -0
  4. package/dist/anomaly.d.ts.map +1 -0
  5. package/dist/anomaly.js +209 -0
  6. package/dist/anomaly.js.map +1 -0
  7. package/dist/badge.d.ts +56 -0
  8. package/dist/badge.d.ts.map +1 -0
  9. package/dist/badge.js +171 -0
  10. package/dist/badge.js.map +1 -0
  11. package/dist/bonds.d.ts +39 -0
  12. package/dist/bonds.d.ts.map +1 -0
  13. package/dist/bonds.js +149 -0
  14. package/dist/bonds.js.map +1 -0
  15. package/dist/challenges.d.ts +18 -0
  16. package/dist/challenges.d.ts.map +1 -0
  17. package/dist/challenges.js +145 -0
  18. package/dist/challenges.js.map +1 -0
  19. package/dist/cold-call.d.ts +74 -0
  20. package/dist/cold-call.d.ts.map +1 -0
  21. package/dist/cold-call.js +176 -0
  22. package/dist/cold-call.js.map +1 -0
  23. package/dist/compliance.d.ts +82 -0
  24. package/dist/compliance.d.ts.map +1 -0
  25. package/dist/compliance.js +478 -0
  26. package/dist/compliance.js.map +1 -0
  27. package/dist/connections.d.ts +63 -0
  28. package/dist/connections.d.ts.map +1 -0
  29. package/dist/connections.js +170 -0
  30. package/dist/connections.js.map +1 -0
  31. package/dist/constants.d.ts +86 -0
  32. package/dist/constants.d.ts.map +1 -0
  33. package/dist/constants.js +124 -0
  34. package/dist/constants.js.map +1 -0
  35. package/dist/credentials.d.ts +190 -0
  36. package/dist/credentials.d.ts.map +1 -0
  37. package/dist/credentials.js +686 -0
  38. package/dist/credentials.js.map +1 -0
  39. package/dist/crypto.d.ts +27 -0
  40. package/dist/crypto.d.ts.map +1 -0
  41. package/dist/crypto.js +75 -0
  42. package/dist/crypto.js.map +1 -0
  43. package/dist/errors.d.ts +17 -0
  44. package/dist/errors.d.ts.map +1 -0
  45. package/dist/errors.js +29 -0
  46. package/dist/errors.js.map +1 -0
  47. package/dist/i18n.d.ts +98 -0
  48. package/dist/i18n.d.ts.map +1 -0
  49. package/dist/i18n.js +1118 -0
  50. package/dist/i18n.js.map +1 -0
  51. package/dist/identity-bridge.d.ts +52 -0
  52. package/dist/identity-bridge.d.ts.map +1 -0
  53. package/dist/identity-bridge.js +228 -0
  54. package/dist/identity-bridge.js.map +1 -0
  55. package/dist/identity-tree.d.ts +47 -0
  56. package/dist/identity-tree.d.ts.map +1 -0
  57. package/dist/identity-tree.js +69 -0
  58. package/dist/identity-tree.js.map +1 -0
  59. package/dist/index.d.ts +55 -0
  60. package/dist/index.d.ts.map +1 -0
  61. package/dist/index.js +86 -0
  62. package/dist/index.js.map +1 -0
  63. package/dist/key-derivation.d.ts +43 -0
  64. package/dist/key-derivation.d.ts.map +1 -0
  65. package/dist/key-derivation.js +212 -0
  66. package/dist/key-derivation.js.map +1 -0
  67. package/dist/lsag.d.ts +23 -0
  68. package/dist/lsag.d.ts.map +1 -0
  69. package/dist/lsag.js +35 -0
  70. package/dist/lsag.js.map +1 -0
  71. package/dist/merkle.d.ts +19 -0
  72. package/dist/merkle.d.ts.map +1 -0
  73. package/dist/merkle.js +155 -0
  74. package/dist/merkle.js.map +1 -0
  75. package/dist/policies.d.ts +22 -0
  76. package/dist/policies.d.ts.map +1 -0
  77. package/dist/policies.js +123 -0
  78. package/dist/policies.js.map +1 -0
  79. package/dist/range-proof.d.ts +6 -0
  80. package/dist/range-proof.d.ts.map +1 -0
  81. package/dist/range-proof.js +45 -0
  82. package/dist/range-proof.js.map +1 -0
  83. package/dist/relay.d.ts +106 -0
  84. package/dist/relay.d.ts.map +1 -0
  85. package/dist/relay.js +336 -0
  86. package/dist/relay.js.map +1 -0
  87. package/dist/ring-signature.d.ts +35 -0
  88. package/dist/ring-signature.d.ts.map +1 -0
  89. package/dist/ring-signature.js +56 -0
  90. package/dist/ring-signature.js.map +1 -0
  91. package/dist/shamir.d.ts +55 -0
  92. package/dist/shamir.d.ts.map +1 -0
  93. package/dist/shamir.js +253 -0
  94. package/dist/shamir.js.map +1 -0
  95. package/dist/signet-words.d.ts +42 -0
  96. package/dist/signet-words.d.ts.map +1 -0
  97. package/dist/signet-words.js +82 -0
  98. package/dist/signet-words.js.map +1 -0
  99. package/dist/store.d.ts +65 -0
  100. package/dist/store.d.ts.map +1 -0
  101. package/dist/store.js +290 -0
  102. package/dist/store.js.map +1 -0
  103. package/dist/trust-score.d.ts +9 -0
  104. package/dist/trust-score.d.ts.map +1 -0
  105. package/dist/trust-score.js +186 -0
  106. package/dist/trust-score.js.map +1 -0
  107. package/dist/types.d.ts +358 -0
  108. package/dist/types.d.ts.map +1 -0
  109. package/dist/types.js +15 -0
  110. package/dist/types.js.map +1 -0
  111. package/dist/utils.d.ts +11 -0
  112. package/dist/utils.d.ts.map +1 -0
  113. package/dist/utils.js +21 -0
  114. package/dist/utils.js.map +1 -0
  115. package/dist/validation.d.ts +33 -0
  116. package/dist/validation.d.ts.map +1 -0
  117. package/dist/validation.js +312 -0
  118. package/dist/validation.js.map +1 -0
  119. package/dist/verifiers.d.ts +18 -0
  120. package/dist/verifiers.d.ts.map +1 -0
  121. package/dist/verifiers.js +118 -0
  122. package/dist/verifiers.js.map +1 -0
  123. package/dist/vouches.d.ts +14 -0
  124. package/dist/vouches.d.ts.map +1 -0
  125. package/dist/vouches.js +103 -0
  126. package/dist/vouches.js.map +1 -0
  127. package/package.json +76 -0
  128. package/src/anomaly.ts +307 -0
  129. package/src/badge.ts +208 -0
  130. package/src/bonds.ts +203 -0
  131. package/src/challenges.ts +187 -0
  132. package/src/cold-call.ts +238 -0
  133. package/src/compliance.ts +612 -0
  134. package/src/connections.ts +216 -0
  135. package/src/constants.ts +146 -0
  136. package/src/credentials.ts +908 -0
  137. package/src/crypto.ts +85 -0
  138. package/src/errors.ts +31 -0
  139. package/src/i18n.ts +1347 -0
  140. package/src/identity-bridge.ts +262 -0
  141. package/src/identity-tree.ts +90 -0
  142. package/src/index.ts +452 -0
  143. package/src/lsag.ts +53 -0
  144. package/src/merkle.ts +176 -0
  145. package/src/policies.ts +154 -0
  146. package/src/range-proof.ts +66 -0
  147. package/src/relay.ts +433 -0
  148. package/src/ring-signature.ts +76 -0
  149. package/src/signet-words.ts +122 -0
  150. package/src/store.ts +336 -0
  151. package/src/trust-score.ts +208 -0
  152. package/src/types.ts +482 -0
  153. package/src/utils.ts +20 -0
  154. package/src/validation.ts +391 -0
  155. package/src/verifiers.ts +156 -0
  156. package/src/vouches.ts +141 -0
package/src/index.ts ADDED
@@ -0,0 +1,452 @@
1
+ // Signet Protocol — TypeScript Library
2
+ // Decentralised identity verification for Nostr
3
+
4
+ // Types
5
+ export type {
6
+ CryptoAlgorithm,
7
+ SignetTier,
8
+ VerificationType,
9
+ VerificationScope,
10
+ VerificationMethod,
11
+ VouchMethod,
12
+ EnforcementLevel,
13
+ ChallengeReason,
14
+ BondAction,
15
+ RevocationScope,
16
+ EntityType,
17
+ EntityMode,
18
+ UnsignedEvent,
19
+ NostrEvent,
20
+ CredentialParams,
21
+ VouchParams,
22
+ PolicyParams,
23
+ PolicyCheckResult,
24
+ VerifierParams,
25
+ ChallengeParams,
26
+ RevocationParams,
27
+ TrustSignal,
28
+ TrustScoreBreakdown,
29
+ MerkleProof,
30
+ SelectiveDisclosure,
31
+ ParsedCredential,
32
+ ParsedVouch,
33
+ ParsedPolicy,
34
+ ParsedVerifier,
35
+ ParsedChallenge,
36
+ ParsedRevocation,
37
+ ParsedDelegation,
38
+ ParsedIdentityBridge,
39
+ TwoCredentialResult,
40
+ CredentialChain,
41
+ GuardianDelegationParams,
42
+ GuardianDelegationScope,
43
+ SimpleEntityType,
44
+ // Bond types
45
+ BitcoinAddressType,
46
+ BondProof,
47
+ BondStatus,
48
+ BondVerificationResult,
49
+ BIP322Verifier,
50
+ } from './types.js';
51
+
52
+ // Entity display labels (value export from types)
53
+ export { ENTITY_DISPLAY_LABELS } from './types.js';
54
+
55
+ // Constants
56
+ export {
57
+ ATTESTATION_KIND,
58
+ APP_DATA_KIND,
59
+ ATTESTATION_TYPES,
60
+ SIGNET_KINDS,
61
+ SIGNET_LABEL,
62
+ DEFAULT_VOUCH_THRESHOLD,
63
+ DEFAULT_VOUCHER_MIN_TIER,
64
+ DEFAULT_CREDENTIAL_EXPIRY_SECONDS,
65
+ DEFAULT_REVOCATION_THRESHOLD,
66
+ VERIFIER_ACTIVATION,
67
+ TRUST_WEIGHTS,
68
+ MAX_TRUST_SCORE,
69
+ SIGNAL_PRIORITY,
70
+ MIN_BRIDGE_RING_SIZE,
71
+ ENTITY_TYPES,
72
+ DELEGATION_CONSTRAINTS,
73
+ ENTITY_LABELS,
74
+ DEFAULT_CRYPTO_ALGORITHM,
75
+ // Bond constants
76
+ BOND_DOMAIN_SEPARATOR,
77
+ DEFAULT_BOND_MAX_AGE_SECS,
78
+ VALID_BOND_ADDRESS_TYPES,
79
+ } from './constants.js';
80
+
81
+ // Crypto
82
+ export {
83
+ generateKeyPair,
84
+ getPublicKey,
85
+ signEvent,
86
+ verifyEvent,
87
+ getEventId,
88
+ hash,
89
+ hashString,
90
+ } from './crypto.js';
91
+
92
+ // Credentials (attestation type: credential)
93
+ export {
94
+ buildCredentialEvent,
95
+ createSelfDeclaredCredential,
96
+ createPeerVouchedCredential,
97
+ createProfessionalCredential,
98
+ createChildSafetyCredential,
99
+ verifyCredential,
100
+ isCredentialExpired,
101
+ parseCredential,
102
+ // Ring-signature protected credentials
103
+ createRingProtectedCredential,
104
+ createRingProtectedChildCredential,
105
+ verifyRingProtectedContent,
106
+ // Credential renewal
107
+ renewCredential,
108
+ needsRenewal,
109
+ // Two-credential ceremony
110
+ createTwoCredentialCeremony,
111
+ // Credential chains
112
+ supersedeCredential,
113
+ resolveCredentialChain,
114
+ isSuperseded,
115
+ // Nullifier utilities
116
+ computeNullifier,
117
+ checkNullifierDuplicate,
118
+ buildNullifierChainTag,
119
+ // Multi-document nullifier families
120
+ computeNullifierFamily,
121
+ buildNullifierFamilyTags,
122
+ checkNullifierFamilyDuplicate,
123
+ // Guardian delegation
124
+ createGuardianDelegation,
125
+ } from './credentials.js';
126
+
127
+ export type { RingProtectedContent, DocumentDescriptor, NullifierFamily } from './credentials.js';
128
+
129
+ // Vouches (attestation type: vouch)
130
+ export {
131
+ buildVouchEvent,
132
+ createVouch,
133
+ parseVouch,
134
+ countQualifyingVouches,
135
+ hasEnoughVouches,
136
+ getVouchers,
137
+ } from './vouches.js';
138
+
139
+ // Policies (NIP-78 kind 30078)
140
+ export {
141
+ buildPolicyEvent,
142
+ createPolicy,
143
+ parsePolicy,
144
+ checkPolicyCompliance,
145
+ PolicyChecker,
146
+ } from './policies.js';
147
+
148
+ // Verifiers (attestation type: verifier)
149
+ export {
150
+ buildVerifierEvent,
151
+ createVerifierCredential,
152
+ parseVerifier,
153
+ checkCrossVerification,
154
+ isVerifierRevoked,
155
+ } from './verifiers.js';
156
+
157
+ // Bonds (proof-of-reserve bond attestation)
158
+ export {
159
+ buildBondMessage,
160
+ createBondProof,
161
+ verifyBondProof,
162
+ isBondStale,
163
+ bondProofToTags,
164
+ parseBondProof,
165
+ checkBondCompliance,
166
+ } from './bonds.js';
167
+
168
+ export type { CreateBondProofParams, VerifyBondProofOptions } from './bonds.js';
169
+
170
+ // Challenges & Revocations (attestation types: challenge, revocation)
171
+ export {
172
+ buildChallengeEvent,
173
+ createChallenge,
174
+ parseChallenge,
175
+ buildRevocationEvent,
176
+ createRevocation,
177
+ parseRevocation,
178
+ countChallengeConfirmations,
179
+ hasReachedRevocationThreshold,
180
+ } from './challenges.js';
181
+
182
+ // Signet Score
183
+ export {
184
+ computeTrustScore,
185
+ formatTrustDisplay,
186
+ verifySignalOrdering,
187
+ } from './trust-score.js';
188
+
189
+ // Merkle Tree
190
+ export {
191
+ MerkleTree,
192
+ verifyMerkleProof,
193
+ verifySelectiveDisclosure,
194
+ } from './merkle.js';
195
+
196
+ // Ring Signatures
197
+ export {
198
+ MAX_RING_SIZE,
199
+ ringSign,
200
+ ringVerify,
201
+ signCredentialRing,
202
+ verifyCredentialRing,
203
+ } from './ring-signature.js';
204
+
205
+ export type { RingSignature } from './ring-signature.js';
206
+
207
+ // LSAG (Linkable Ring Signatures)
208
+ export {
209
+ MAX_RING_SIZE as LSAG_MAX_RING_SIZE,
210
+ computeKeyImage,
211
+ lsagSign,
212
+ lsagVerify,
213
+ hasDuplicateKeyImage,
214
+ } from './lsag.js';
215
+
216
+ export type { LsagSignature } from './lsag.js';
217
+
218
+ // Identity Bridge (attestation type: identity-bridge)
219
+ export {
220
+ createIdentityBridge,
221
+ verifyIdentityBridge,
222
+ parseIdentityBridge,
223
+ selectDecoyRing,
224
+ computeBridgeWeight,
225
+ } from './identity-bridge.js';
226
+
227
+ // Range Proofs (Pedersen Commitments)
228
+ export {
229
+ commit,
230
+ verifyCommitment,
231
+ createRangeProof,
232
+ verifyRangeProof,
233
+ createAgeRangeProof,
234
+ verifyAgeRangeProof,
235
+ serializeRangeProof,
236
+ deserializeRangeProof,
237
+ } from './range-proof.js';
238
+
239
+ export type { PedersenCommitment, RangeProof } from './range-proof.js';
240
+
241
+ // Relay Client
242
+ export {
243
+ RelayClient,
244
+ publishToRelays,
245
+ fetchFromRelay,
246
+ } from './relay.js';
247
+
248
+ export type {
249
+ RelayMessage,
250
+ NostrFilter,
251
+ SubscriptionCallback,
252
+ RelayState,
253
+ RelayOptions,
254
+ } from './relay.js';
255
+
256
+ // Event Store
257
+ export { SignetStore } from './store.js';
258
+ export type { StoreQuery } from './store.js';
259
+
260
+ // Anomaly Detection
261
+ export {
262
+ detectAnomalies,
263
+ scanForAnomalies,
264
+ } from './anomaly.js';
265
+
266
+ export type {
267
+ AnomalyType,
268
+ AnomalyFlag,
269
+ AnomalyConfig,
270
+ } from './anomaly.js';
271
+
272
+ // Jurisdictions (re-exported from jurisdiction-kit)
273
+ export {
274
+ JURISDICTIONS,
275
+ getJurisdiction,
276
+ getJurisdictionCodes,
277
+ getProfessionalBodies,
278
+ getMutualRecognitionPartners,
279
+ isProfessionRegulated,
280
+ findJurisdictionsForProfession,
281
+ getDigitalConsentAge,
282
+ getAgeOfMajority,
283
+ canTransferData,
284
+ getAllLanguages,
285
+ getJurisdictionsByLanguage,
286
+ computeJurisdictionConfidence,
287
+ getJurisdictionConfidence,
288
+ rankJurisdictionsByConfidence,
289
+ } from 'jurisdiction-kit';
290
+
291
+ export type {
292
+ LegalSystem,
293
+ ProfessionType,
294
+ ProfessionalBody,
295
+ DataProtectionLaw,
296
+ ChildProtectionLaw,
297
+ Jurisdiction,
298
+ JurisdictionConfidence,
299
+ } from 'jurisdiction-kit';
300
+
301
+ // Internationalization
302
+ export {
303
+ setLanguage,
304
+ getLanguage,
305
+ getSupportedLanguages,
306
+ t,
307
+ getTranslations,
308
+ getLanguageName,
309
+ getLanguageNativeName,
310
+ formatLocalizedTrustScore,
311
+ getTierDescription,
312
+ } from './i18n.js';
313
+
314
+ export type { LanguageCode, TranslationStrings } from './i18n.js';
315
+
316
+ // Compliance
317
+ export {
318
+ checkCredentialCompliance,
319
+ checkCrossBorderCompliance,
320
+ checkChildCompliance,
321
+ getConsentRequirements,
322
+ getRetentionGuidance,
323
+ checkMultiJurisdictionCompliance,
324
+ getMostRestrictiveRequirements,
325
+ } from './compliance.js';
326
+
327
+ export type {
328
+ ComplianceSeverity,
329
+ ComplianceIssue,
330
+ ComplianceResult,
331
+ CrossBorderResult,
332
+ ChildComplianceResult,
333
+ ConsentRequirement,
334
+ } from './compliance.js';
335
+
336
+ // Validation
337
+ export {
338
+ validateCredential,
339
+ validateVouch,
340
+ validatePolicy,
341
+ validateVerifier,
342
+ validateChallenge,
343
+ validateRevocation,
344
+ validateEvent,
345
+ getTagValue,
346
+ getTagValues,
347
+ } from './validation.js';
348
+
349
+ export type { ValidationResult } from './validation.js';
350
+
351
+ // Errors
352
+ export {
353
+ SignetError,
354
+ SignetValidationError,
355
+ SignetCryptoError,
356
+ } from './errors.js';
357
+
358
+ // BIP-39 Wordlist
359
+ export { wordlist as BIP39_WORDLIST } from '@scure/bip39/wordlists/english.js';
360
+
361
+ // Mnemonic (BIP-39)
362
+ export {
363
+ generateMnemonic,
364
+ mnemonicToEntropy,
365
+ entropyToMnemonic,
366
+ validateMnemonic,
367
+ } from '@scure/bip39';
368
+ export { mnemonicToSeedSync as mnemonicToSeed } from '@scure/bip39';
369
+
370
+ // Identity Tree (nsec-tree integration)
371
+ export {
372
+ createSignetIdentity,
373
+ createSignetIdentityFromNsec,
374
+ deriveAdditionalPersona,
375
+ deriveSubIdentity,
376
+ createLinkageProof,
377
+ verifyLinkageProof,
378
+ destroyIdentity,
379
+ NATURAL_PERSON_PERSONA,
380
+ ANONYMOUS_PERSONA,
381
+ } from './identity-tree.js';
382
+
383
+ export type { SignetIdentity } from './identity-tree.js';
384
+
385
+ // nsec-tree re-exports
386
+ export { zeroise } from 'nsec-tree';
387
+ export type { TreeRoot, Identity, Persona, LinkageProof } from 'nsec-tree';
388
+
389
+ // NIP-19 encoding (nsec-tree/encoding)
390
+ export { encodeNsec, decodeNsec, encodeNpub, decodeNpub } from 'nsec-tree/encoding';
391
+
392
+ // Shamir's Secret Sharing
393
+ export {
394
+ splitSecret,
395
+ reconstructSecret,
396
+ shareToWords,
397
+ wordsToShare,
398
+ } from '@forgesworn/shamir-words';
399
+
400
+ export type { ShamirShare } from '@forgesworn/shamir-words';
401
+
402
+ // Connections (ECDH / QR Exchange)
403
+ export {
404
+ computeSharedSecret,
405
+ createQRPayload,
406
+ serializeQRPayload,
407
+ parseQRPayload,
408
+ createConnection,
409
+ ConnectionStore,
410
+ } from './connections.js';
411
+
412
+ export type { ContactInfo, Connection, QRPayload } from './connections.js';
413
+
414
+ // Badge Display (Level 1 integration)
415
+ export {
416
+ computeBadge,
417
+ getTrustLevel,
418
+ meetsMinimumTier,
419
+ filterEventsForPubkey,
420
+ buildBadgeFilters,
421
+ } from './badge.js';
422
+
423
+ export type { BadgeInfo, TrustLevel } from './badge.js';
424
+
425
+ // Signet Words (Time-Based Verification — powered by spoken-token)
426
+ export {
427
+ SIGNET_EPOCH_SECONDS,
428
+ SIGNET_WORD_COUNT,
429
+ SIGNET_TOLERANCE,
430
+ MAX_WORD_COUNT,
431
+ SIGNET_WORDLIST,
432
+ getEpoch,
433
+ deriveWords,
434
+ getSignetWords,
435
+ verifySignetWords,
436
+ formatSignetWords,
437
+ getSignetDisplay,
438
+ } from './signet-words.js';
439
+
440
+ export type { SignetWordsConfig } from './signet-words.js';
441
+
442
+ // Cold-Call Verification (institutional caller verification via .well-known/signet.json + ephemeral ECDH)
443
+ export {
444
+ fetchInstitutionKeys,
445
+ generateSessionCode,
446
+ deriveColdCallWords,
447
+ initiateColdCallVerification,
448
+ completeColdCallVerification,
449
+ type ColdCallSession,
450
+ } from './cold-call.js';
451
+
452
+ export type { InstitutionKeys, InstitutionPubkey } from './types.js';
package/src/lsag.ts ADDED
@@ -0,0 +1,53 @@
1
+ // LSAG (Linkable Spontaneous Anonymous Group) Ring Signatures
2
+ // Thin re-export from @forgesworn/ring-sig with Signet domain separator.
3
+ // Extends SAG with a key image that links signatures by the same signer
4
+ // across multiple uses of the same election, enabling double-vote detection.
5
+
6
+ import {
7
+ computeKeyImage as _computeKeyImage,
8
+ hasDuplicateKeyImage as _hasDuplicateKeyImage,
9
+ lsagSign as _lsagSign,
10
+ lsagVerify as _lsagVerify,
11
+ type LsagSignature,
12
+ MAX_RING_SIZE,
13
+ } from '@forgesworn/ring-sig';
14
+
15
+ const SIGNET_LSAG_DOMAIN = 'signet-lsag-v1';
16
+
17
+ export { type LsagSignature, MAX_RING_SIZE };
18
+
19
+ // Key image functions do not involve challenge hashes — safe to re-export directly.
20
+ export const computeKeyImage = _computeKeyImage;
21
+ export const hasDuplicateKeyImage = _hasDuplicateKeyImage;
22
+
23
+ /**
24
+ * Sign with LSAG using Signet's domain separator.
25
+ *
26
+ * @param message - The message to sign (typically `electionId:SHA-256(encryptedVote)`)
27
+ * @param ring - Array of x-only public keys (hex) forming the anonymity set
28
+ * @param signerIndex - Index of the actual signer in the ring
29
+ * @param privateKey - Signer's private key (hex)
30
+ * @param electionId - Election identifier for key image linkability
31
+ * @returns A linkable ring signature with Signet domain
32
+ */
33
+ export function lsagSign(
34
+ message: string,
35
+ ring: string[],
36
+ signerIndex: number,
37
+ privateKey: string,
38
+ electionId: string,
39
+ ): LsagSignature {
40
+ const sig = _lsagSign(message, ring, signerIndex, privateKey, electionId, SIGNET_LSAG_DOMAIN);
41
+ sig.domain = SIGNET_LSAG_DOMAIN;
42
+ return sig;
43
+ }
44
+
45
+ /**
46
+ * Verify an LSAG signature.
47
+ *
48
+ * @param sig - The LSAG signature to verify
49
+ * @returns true if the signature is valid
50
+ */
51
+ export function lsagVerify(sig: LsagSignature): boolean {
52
+ return _lsagVerify(sig);
53
+ }
package/src/merkle.ts ADDED
@@ -0,0 +1,176 @@
1
+ // Merkle Tree Selective Disclosure
2
+ // Credential attributes as Merkle leaves — reveal chosen attributes + sibling paths
3
+
4
+ import { sha256 } from '@noble/hashes/sha2.js';
5
+ import { bytesToHex, hexToBytes, utf8ToBytes } from '@noble/hashes/utils.js';
6
+ import type { MerkleProof, SelectiveDisclosure } from './types.js';
7
+ import { SignetValidationError } from './errors.js';
8
+
9
+ /**
10
+ * Hash a leaf node with domain separation prefix 0x00.
11
+ * Leaf hash: SHA-256(0x00 || "key:value")
12
+ * RFC 6962 domain separation prevents second-preimage attacks.
13
+ */
14
+ function hashLeaf(data: string): string {
15
+ const prefix = new Uint8Array([0x00]);
16
+ const content = utf8ToBytes(data);
17
+ const combined = new Uint8Array(1 + content.length);
18
+ combined.set(prefix);
19
+ combined.set(content, 1);
20
+ return bytesToHex(sha256(combined));
21
+ }
22
+
23
+ /**
24
+ * Hash an internal node with domain separation prefix 0x01.
25
+ * Internal node hash: SHA-256(0x01 || left || right)
26
+ * RFC 6962 domain separation prevents second-preimage attacks.
27
+ */
28
+ function hashInternal(left: string, right: string): string {
29
+ const prefix = new Uint8Array([0x01]);
30
+ const leftBytes = hexToBytes(left);
31
+ const rightBytes = hexToBytes(right);
32
+ const combined = new Uint8Array(1 + leftBytes.length + rightBytes.length);
33
+ combined.set(prefix);
34
+ combined.set(leftBytes, 1);
35
+ combined.set(rightBytes, 1 + leftBytes.length);
36
+ return bytesToHex(sha256(combined));
37
+ }
38
+
39
+ /** Combine two child hashes into a parent node.
40
+ * Order is determined by tree position (left/right), NOT by hash value.
41
+ * Sorting would allow proof transplanting between sibling positions. */
42
+ function hashPairOrdered(left: string, right: string): string {
43
+ return hashInternal(left, right);
44
+ }
45
+
46
+ /** Build a Merkle tree from leaf values. Returns all levels (bottom-up). */
47
+ function buildTree(leaves: string[]): string[][] {
48
+ if (leaves.length === 0) throw new SignetValidationError('Cannot build tree from empty leaves');
49
+
50
+ // Pad to power of 2
51
+ const paddedLeaves = [...leaves];
52
+ while (paddedLeaves.length & (paddedLeaves.length - 1)) {
53
+ paddedLeaves.push(hashLeaf('__padding__' + paddedLeaves.length));
54
+ }
55
+
56
+ const levels: string[][] = [paddedLeaves];
57
+ let current = paddedLeaves;
58
+
59
+ while (current.length > 1) {
60
+ const next: string[] = [];
61
+ for (let i = 0; i < current.length; i += 2) {
62
+ next.push(hashPairOrdered(current[i], current[i + 1]));
63
+ }
64
+ levels.push(next);
65
+ current = next;
66
+ }
67
+
68
+ return levels;
69
+ }
70
+
71
+ export class MerkleTree {
72
+ private levels: string[][];
73
+ private leafHashes: string[];
74
+ readonly root: string;
75
+
76
+ constructor(private attributes: Record<string, string>) {
77
+ for (const k of Object.keys(attributes)) {
78
+ if (k.includes(':')) throw new SignetValidationError(`Merkle attribute key must not contain ':': "${k}"`);
79
+ }
80
+ const entries = Object.entries(attributes).sort(([a], [b]) => a.localeCompare(b));
81
+ // Each leaf is hashLeaf("key:value") — domain-separated with 0x00 prefix
82
+ this.leafHashes = entries.map(([k, v]) => hashLeaf(`${k}:${v}`));
83
+ this.levels = buildTree(this.leafHashes);
84
+ this.root = this.levels[this.levels.length - 1][0];
85
+ }
86
+
87
+ /** Get the Merkle root */
88
+ getRoot(): string {
89
+ return this.root;
90
+ }
91
+
92
+ /** Generate a proof for a specific attribute */
93
+ prove(key: string): MerkleProof {
94
+ const entries = Object.entries(this.attributes).sort(([a], [b]) => a.localeCompare(b));
95
+ const idx = entries.findIndex(([k]) => k === key);
96
+ if (idx === -1) throw new SignetValidationError(`Attribute "${key}" not found`);
97
+
98
+ const leafHash = this.leafHashes[idx];
99
+ const siblings: string[] = [];
100
+ let currentIdx = idx;
101
+
102
+ // Pad index space to match padded tree
103
+ for (let level = 0; level < this.levels.length - 1; level++) {
104
+ const siblingIdx = currentIdx ^ 1; // flip last bit to get sibling
105
+ if (siblingIdx < this.levels[level].length) {
106
+ siblings.push(this.levels[level][siblingIdx]);
107
+ }
108
+ currentIdx = currentIdx >> 1;
109
+ }
110
+
111
+ return {
112
+ leaf: leafHash,
113
+ index: idx,
114
+ siblings,
115
+ root: this.root,
116
+ };
117
+ }
118
+
119
+ /** Create a selective disclosure revealing only specified attributes */
120
+ disclose(keys: string[]): SelectiveDisclosure {
121
+ const revealedAttributes: Record<string, string> = {};
122
+ const proofs: MerkleProof[] = [];
123
+
124
+ for (const key of keys) {
125
+ const entries = Object.entries(this.attributes).sort(([a], [b]) => a.localeCompare(b));
126
+ const entry = entries.find(([k]) => k === key);
127
+ if (!entry) throw new SignetValidationError(`Attribute "${key}" not found`);
128
+
129
+ revealedAttributes[key] = entry[1];
130
+ proofs.push(this.prove(key));
131
+ }
132
+
133
+ return {
134
+ revealedAttributes,
135
+ proofs,
136
+ merkleRoot: this.root,
137
+ };
138
+ }
139
+ }
140
+
141
+ /** Verify a Merkle proof against a root */
142
+ export function verifyMerkleProof(
143
+ key: string,
144
+ value: string,
145
+ proof: MerkleProof
146
+ ): boolean {
147
+ let currentHash = hashLeaf(`${key}:${value}`);
148
+ if (currentHash !== proof.leaf) return false;
149
+
150
+ let idx = proof.index;
151
+ for (const sibling of proof.siblings) {
152
+ // Use index bit to determine left/right position — NOT sorted ordering
153
+ currentHash = (idx & 1) === 0
154
+ ? hashPairOrdered(currentHash, sibling)
155
+ : hashPairOrdered(sibling, currentHash);
156
+ idx = idx >> 1;
157
+ }
158
+
159
+ return currentHash === proof.root;
160
+ }
161
+
162
+ /** Verify an entire selective disclosure */
163
+ export function verifySelectiveDisclosure(disclosure: SelectiveDisclosure): boolean {
164
+ const entries = Object.entries(disclosure.revealedAttributes);
165
+ if (entries.length !== disclosure.proofs.length) return false;
166
+
167
+ for (let i = 0; i < entries.length; i++) {
168
+ const [key, value] = entries[i];
169
+ const proof = disclosure.proofs[i];
170
+
171
+ if (proof.root !== disclosure.merkleRoot) return false;
172
+ if (!verifyMerkleProof(key, value, proof)) return false;
173
+ }
174
+
175
+ return true;
176
+ }