@stvor/sdk 2.4.1 → 3.0.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 (81) hide show
  1. package/dist/facade/app.d.ts +83 -76
  2. package/dist/facade/app.js +330 -195
  3. package/dist/facade/crypto-session.cjs +29 -0
  4. package/dist/facade/crypto-session.d.ts +71 -0
  5. package/dist/facade/crypto-session.js +152 -0
  6. package/dist/facade/errors.d.ts +29 -12
  7. package/dist/facade/errors.js +49 -8
  8. package/dist/facade/index.d.ts +27 -8
  9. package/dist/facade/index.js +23 -3
  10. package/dist/facade/local-storage-identity-store.cjs +29 -0
  11. package/dist/facade/local-storage-identity-store.d.ts +50 -0
  12. package/dist/facade/local-storage-identity-store.js +100 -0
  13. package/dist/facade/metrics-attestation.cjs +29 -0
  14. package/dist/facade/metrics-attestation.d.ts +209 -0
  15. package/dist/facade/metrics-attestation.js +333 -0
  16. package/dist/facade/metrics-engine.cjs +29 -0
  17. package/dist/facade/metrics-engine.d.ts +91 -0
  18. package/dist/facade/metrics-engine.js +170 -0
  19. package/dist/facade/redis-replay-cache.cjs +29 -0
  20. package/dist/facade/redis-replay-cache.d.ts +88 -0
  21. package/dist/facade/redis-replay-cache.js +60 -0
  22. package/dist/facade/relay-client.d.ts +22 -23
  23. package/dist/facade/relay-client.js +107 -128
  24. package/dist/facade/replay-manager.cjs +29 -0
  25. package/dist/facade/replay-manager.d.ts +51 -0
  26. package/dist/facade/replay-manager.js +150 -0
  27. package/dist/facade/sodium-singleton.cjs +29 -0
  28. package/dist/facade/sodium-singleton.d.ts +20 -0
  29. package/dist/facade/sodium-singleton.js +44 -0
  30. package/dist/facade/tofu-manager.cjs +29 -0
  31. package/dist/facade/tofu-manager.d.ts +82 -0
  32. package/dist/facade/tofu-manager.js +166 -0
  33. package/dist/facade/types.d.ts +2 -0
  34. package/dist/index.d.cts +4 -0
  35. package/dist/index.d.ts +4 -0
  36. package/dist/index.js +7 -0
  37. package/dist/legacy.d.ts +31 -1
  38. package/dist/legacy.js +90 -2
  39. package/dist/ratchet/core-production.cjs +29 -0
  40. package/dist/ratchet/core-production.d.ts +95 -0
  41. package/dist/ratchet/core-production.js +286 -0
  42. package/dist/{facade/crypto.cjs → ratchet/index.cjs} +1 -1
  43. package/dist/ratchet/index.d.ts +59 -0
  44. package/dist/ratchet/index.js +343 -0
  45. package/dist/ratchet/key-recovery.cjs +29 -0
  46. package/dist/ratchet/key-recovery.d.ts +45 -0
  47. package/dist/ratchet/key-recovery.js +148 -0
  48. package/dist/ratchet/replay-protection.cjs +29 -0
  49. package/dist/ratchet/replay-protection.d.ts +21 -0
  50. package/dist/ratchet/replay-protection.js +50 -0
  51. package/dist/{mock-relay-server.cjs → ratchet/tofu.cjs} +1 -1
  52. package/dist/ratchet/tofu.d.ts +27 -0
  53. package/dist/ratchet/tofu.js +62 -0
  54. package/dist/src/facade/app.cjs +29 -0
  55. package/dist/src/facade/app.d.ts +105 -0
  56. package/dist/src/facade/app.js +245 -0
  57. package/dist/src/facade/crypto.cjs +29 -0
  58. package/dist/src/facade/errors.cjs +29 -0
  59. package/dist/src/facade/errors.d.ts +19 -0
  60. package/dist/src/facade/errors.js +21 -0
  61. package/dist/src/facade/index.cjs +29 -0
  62. package/dist/src/facade/index.d.ts +8 -0
  63. package/dist/src/facade/index.js +5 -0
  64. package/dist/src/facade/relay-client.cjs +29 -0
  65. package/dist/src/facade/relay-client.d.ts +36 -0
  66. package/dist/src/facade/relay-client.js +154 -0
  67. package/dist/src/facade/types.cjs +29 -0
  68. package/dist/src/facade/types.d.ts +50 -0
  69. package/dist/src/facade/types.js +4 -0
  70. package/dist/src/index.cjs +29 -0
  71. package/dist/src/index.d.ts +2 -0
  72. package/dist/src/index.js +2 -0
  73. package/dist/src/legacy.cjs +29 -0
  74. package/dist/src/legacy.d.ts +0 -0
  75. package/dist/src/legacy.js +1 -0
  76. package/dist/src/mock-relay-server.cjs +29 -0
  77. package/package.json +16 -5
  78. /package/dist/{facade → src/facade}/crypto.d.ts +0 -0
  79. /package/dist/{facade → src/facade}/crypto.js +0 -0
  80. /package/dist/{mock-relay-server.d.ts → src/mock-relay-server.d.ts} +0 -0
  81. /package/dist/{mock-relay-server.js → src/mock-relay-server.js} +0 -0
@@ -0,0 +1,29 @@
1
+ 'use strict';
2
+
3
+ // Auto-generated CommonJS wrapper for facade/replay-manager.js
4
+ // This allows `require('@stvor/sdk')` to work alongside ESM `import`.
5
+
6
+ const mod = require('module');
7
+ const url = require('url');
8
+
9
+ // Use dynamic import to load the ESM module
10
+ let _cached;
11
+ async function _load() {
12
+ if (!_cached) {
13
+ _cached = await import(url.pathToFileURL(__filename.replace(/\.cjs$/, '.js')).href);
14
+ }
15
+ return _cached;
16
+ }
17
+
18
+ // For simple CJS usage, expose a promise-based loader
19
+ module.exports = new Proxy({ load: _load }, {
20
+ get(target, prop) {
21
+ if (prop === '__esModule') return true;
22
+ if (prop === 'then') return undefined; // prevent treating as thenable
23
+ if (prop === 'load') return _load;
24
+ if (prop === 'default') {
25
+ return _load().then(m => m.default);
26
+ }
27
+ return _load().then(m => m[prop]);
28
+ }
29
+ });
@@ -0,0 +1,51 @@
1
+ /**
2
+ * STVOR Replay Protection Manager
3
+ * Implements persistent replay protection with fallback to in-memory
4
+ *
5
+ * VERSION 2.0 - PRODUCTION READY
6
+ *
7
+ * Features:
8
+ * - Persistent storage interface (Redis, PostgreSQL, etc.)
9
+ * - In-memory fallback for development
10
+ * - Proper cleanup of expired entries
11
+ * - Cache statistics
12
+ */
13
+ export interface IReplayCache {
14
+ addNonce(userId: string, nonce: string, timestamp: number): Promise<void>;
15
+ hasNonce(userId: string, nonce: string): Promise<boolean>;
16
+ cleanup(userId: string, maxAge: number): Promise<number>;
17
+ getStats(): Promise<{
18
+ size: number;
19
+ }>;
20
+ }
21
+ /**
22
+ * Initialize replay protection with optional persistent storage
23
+ */
24
+ export declare function initializeReplayProtection(customCache?: IReplayCache): void;
25
+ /**
26
+ * Check if message is a replay attack
27
+ */
28
+ export declare function isReplay(userId: string, nonce: string, timestamp: number): Promise<boolean>;
29
+ /**
30
+ * Validate message for replay protection
31
+ * Throws error if replay detected or message too old
32
+ */
33
+ export declare function validateMessage(userId: string, nonce: string, timestamp: number): Promise<void>;
34
+ /**
35
+ * Validate message with Uint8Array nonce
36
+ */
37
+ export declare function validateMessageWithNonce(userId: string, nonce: Uint8Array, timestamp: number): Promise<void>;
38
+ /**
39
+ * Cleanup expired nonces from cache
40
+ * Should be called periodically in production
41
+ */
42
+ export declare function cleanupExpiredNonces(): Promise<number>;
43
+ /**
44
+ * Get cache statistics (for monitoring)
45
+ */
46
+ export declare function getCacheStats(): Promise<{
47
+ size: number;
48
+ maxSize: number;
49
+ }>;
50
+ export declare function startAutoCleanup(): void;
51
+ export declare function stopAutoCleanup(): void;
@@ -0,0 +1,150 @@
1
+ /**
2
+ * STVOR Replay Protection Manager
3
+ * Implements persistent replay protection with fallback to in-memory
4
+ *
5
+ * VERSION 2.0 - PRODUCTION READY
6
+ *
7
+ * Features:
8
+ * - Persistent storage interface (Redis, PostgreSQL, etc.)
9
+ * - In-memory fallback for development
10
+ * - Proper cleanup of expired entries
11
+ * - Cache statistics
12
+ */
13
+ // In-memory replay cache (fallback)
14
+ class InMemoryReplayCache {
15
+ constructor() {
16
+ this.cache = new Map();
17
+ this.maxSize = 10000;
18
+ }
19
+ async addNonce(userId, nonce, timestamp) {
20
+ const key = `${userId}:${nonce}`;
21
+ this.cache.set(key, { timestamp: Date.now(), userId });
22
+ // Prevent memory exhaustion
23
+ if (this.cache.size > this.maxSize) {
24
+ await this.cleanupOldest(1000);
25
+ }
26
+ }
27
+ async hasNonce(userId, nonce) {
28
+ return this.cache.has(`${userId}:${nonce}`);
29
+ }
30
+ async cleanup(userId, maxAge) {
31
+ const now = Date.now();
32
+ let cleaned = 0;
33
+ for (const [key, value] of this.cache.entries()) {
34
+ if (value.userId === userId && now - value.timestamp > maxAge) {
35
+ this.cache.delete(key);
36
+ cleaned++;
37
+ }
38
+ }
39
+ return cleaned;
40
+ }
41
+ async getStats() {
42
+ return { size: this.cache.size };
43
+ }
44
+ async cleanupOldest(count) {
45
+ const entries = Array.from(this.cache.entries());
46
+ entries.sort((a, b) => a[1].timestamp - b[1].timestamp);
47
+ for (let i = 0; i < Math.min(count, entries.length); i++) {
48
+ this.cache.delete(entries[i][0]);
49
+ }
50
+ }
51
+ }
52
+ // Global in-memory cache instance
53
+ let globalReplayCache = null;
54
+ // Configuration
55
+ const NONCE_EXPIRY_MS = 5 * 60 * 1000; // 5 minutes - production should be shorter
56
+ const MESSAGE_EXPIRY_MS = 60 * 1000; // Messages older than 1 minute are rejected
57
+ /**
58
+ * Initialize replay protection with optional persistent storage
59
+ */
60
+ export function initializeReplayProtection(customCache) {
61
+ globalReplayCache = customCache || new InMemoryReplayCache();
62
+ console.log('[ReplayProtection] Initialized' + (customCache ? ' with persistent storage' : ' with in-memory fallback'));
63
+ }
64
+ /**
65
+ * Check if message is a replay attack
66
+ */
67
+ export async function isReplay(userId, nonce, timestamp) {
68
+ if (!globalReplayCache) {
69
+ initializeReplayProtection();
70
+ }
71
+ const now = Date.now();
72
+ // CRITICAL: Check if message is too old FIRST (before checking cache)
73
+ // This prevents replay of old messages that have expired
74
+ const messageAge = now - timestamp * 1000;
75
+ if (messageAge > MESSAGE_EXPIRY_MS) {
76
+ throw new Error(`Message rejected: too old (${Math.round(messageAge / 1000)}s)`);
77
+ }
78
+ // Check if nonce already seen
79
+ const isDuplicate = await globalReplayCache.hasNonce(userId, nonce);
80
+ if (isDuplicate) {
81
+ console.warn(`[ReplayProtection] Replay detected from user ${userId}`);
82
+ return true;
83
+ }
84
+ // Store nonce with timestamp
85
+ await globalReplayCache.addNonce(userId, nonce, timestamp);
86
+ return false;
87
+ }
88
+ /**
89
+ * Validate message for replay protection
90
+ * Throws error if replay detected or message too old
91
+ */
92
+ export async function validateMessage(userId, nonce, timestamp) {
93
+ const replay = await isReplay(userId, nonce, timestamp);
94
+ if (replay) {
95
+ throw new Error(`Replay attack detected from user ${userId}`);
96
+ }
97
+ }
98
+ /**
99
+ * Validate message with Uint8Array nonce
100
+ */
101
+ export async function validateMessageWithNonce(userId, nonce, timestamp) {
102
+ const nonceHex = Buffer.from(nonce).toString('hex');
103
+ await validateMessage(userId, nonceHex, timestamp);
104
+ }
105
+ /**
106
+ * Cleanup expired nonces from cache
107
+ * Should be called periodically in production
108
+ */
109
+ export async function cleanupExpiredNonces() {
110
+ if (!globalReplayCache) {
111
+ return 0;
112
+ }
113
+ const cleaned = await globalReplayCache.cleanup('all', NONCE_EXPIRY_MS);
114
+ if (cleaned > 0) {
115
+ console.log(`[ReplayProtection] Cleaned ${cleaned} expired nonces`);
116
+ }
117
+ return cleaned;
118
+ }
119
+ /**
120
+ * Get cache statistics (for monitoring)
121
+ */
122
+ export async function getCacheStats() {
123
+ const stats = await globalReplayCache?.getStats() || { size: 0 };
124
+ return {
125
+ size: stats.size,
126
+ maxSize: 10000,
127
+ };
128
+ }
129
+ // Auto-cleanup interval (every 1 minute)
130
+ let cleanupInterval = null;
131
+ export function startAutoCleanup() {
132
+ if (cleanupInterval) {
133
+ return;
134
+ }
135
+ cleanupInterval = setInterval(() => {
136
+ cleanupExpiredNonces().catch(err => {
137
+ console.error('[ReplayProtection] Cleanup error:', err);
138
+ });
139
+ }, 60000);
140
+ console.log('[ReplayProtection] Auto-cleanup started');
141
+ }
142
+ export function stopAutoCleanup() {
143
+ if (cleanupInterval) {
144
+ clearInterval(cleanupInterval);
145
+ cleanupInterval = null;
146
+ console.log('[ReplayProtection] Auto-cleanup stopped');
147
+ }
148
+ }
149
+ // Default initialization
150
+ initializeReplayProtection();
@@ -0,0 +1,29 @@
1
+ 'use strict';
2
+
3
+ // Auto-generated CommonJS wrapper for facade/sodium-singleton.js
4
+ // This allows `require('@stvor/sdk')` to work alongside ESM `import`.
5
+
6
+ const mod = require('module');
7
+ const url = require('url');
8
+
9
+ // Use dynamic import to load the ESM module
10
+ let _cached;
11
+ async function _load() {
12
+ if (!_cached) {
13
+ _cached = await import(url.pathToFileURL(__filename.replace(/\.cjs$/, '.js')).href);
14
+ }
15
+ return _cached;
16
+ }
17
+
18
+ // For simple CJS usage, expose a promise-based loader
19
+ module.exports = new Proxy({ load: _load }, {
20
+ get(target, prop) {
21
+ if (prop === '__esModule') return true;
22
+ if (prop === 'then') return undefined; // prevent treating as thenable
23
+ if (prop === 'load') return _load;
24
+ if (prop === 'default') {
25
+ return _load().then(m => m.default);
26
+ }
27
+ return _load().then(m => m[prop]);
28
+ }
29
+ });
@@ -0,0 +1,20 @@
1
+ /**
2
+ * STVOR libsodium Singleton
3
+ * Ensures sodium.ready is called only ONCE globally
4
+ * Prevents race conditions during concurrent initialization
5
+ */
6
+ /**
7
+ * Initialize libsodium ONCE globally
8
+ * Safe to call multiple times - returns same promise
9
+ *
10
+ * @throws Never throws - libsodium.ready is infallible
11
+ */
12
+ export declare function ensureSodiumReady(): Promise<void>;
13
+ /**
14
+ * Check if libsodium is ready (synchronous)
15
+ */
16
+ export declare function isSodiumReady(): boolean;
17
+ /**
18
+ * Reset state (ONLY for testing)
19
+ */
20
+ export declare function _resetSodiumState(): void;
@@ -0,0 +1,44 @@
1
+ /**
2
+ * STVOR libsodium Singleton
3
+ * Ensures sodium.ready is called only ONCE globally
4
+ * Prevents race conditions during concurrent initialization
5
+ */
6
+ import sodium from 'libsodium-wrappers';
7
+ let sodiumInitialized = false;
8
+ let sodiumInitPromise = null;
9
+ /**
10
+ * Initialize libsodium ONCE globally
11
+ * Safe to call multiple times - returns same promise
12
+ *
13
+ * @throws Never throws - libsodium.ready is infallible
14
+ */
15
+ export async function ensureSodiumReady() {
16
+ // Already initialized - return immediately
17
+ if (sodiumInitialized) {
18
+ return;
19
+ }
20
+ // Initialization in progress - return existing promise
21
+ if (sodiumInitPromise) {
22
+ return sodiumInitPromise;
23
+ }
24
+ // Start initialization
25
+ sodiumInitPromise = (async () => {
26
+ await sodium.ready;
27
+ sodiumInitialized = true;
28
+ console.log('[Crypto] libsodium initialized');
29
+ })();
30
+ return sodiumInitPromise;
31
+ }
32
+ /**
33
+ * Check if libsodium is ready (synchronous)
34
+ */
35
+ export function isSodiumReady() {
36
+ return sodiumInitialized;
37
+ }
38
+ /**
39
+ * Reset state (ONLY for testing)
40
+ */
41
+ export function _resetSodiumState() {
42
+ sodiumInitialized = false;
43
+ sodiumInitPromise = null;
44
+ }
@@ -0,0 +1,29 @@
1
+ 'use strict';
2
+
3
+ // Auto-generated CommonJS wrapper for facade/tofu-manager.js
4
+ // This allows `require('@stvor/sdk')` to work alongside ESM `import`.
5
+
6
+ const mod = require('module');
7
+ const url = require('url');
8
+
9
+ // Use dynamic import to load the ESM module
10
+ let _cached;
11
+ async function _load() {
12
+ if (!_cached) {
13
+ _cached = await import(url.pathToFileURL(__filename.replace(/\.cjs$/, '.js')).href);
14
+ }
15
+ return _cached;
16
+ }
17
+
18
+ // For simple CJS usage, expose a promise-based loader
19
+ module.exports = new Proxy({ load: _load }, {
20
+ get(target, prop) {
21
+ if (prop === '__esModule') return true;
22
+ if (prop === 'then') return undefined; // prevent treating as thenable
23
+ if (prop === 'load') return _load;
24
+ if (prop === 'default') {
25
+ return _load().then(m => m.default);
26
+ }
27
+ return _load().then(m => m[prop]);
28
+ }
29
+ });
@@ -0,0 +1,82 @@
1
+ /**
2
+ * STVOR TOFU (Trust On First Use) Manager
3
+ * Implements persistent TOFU with fallback to in-memory
4
+ *
5
+ * VERSION 2.0 - PRODUCTION READY
6
+ *
7
+ * Features:
8
+ * - Persistent storage interface
9
+ * - In-memory fallback for development
10
+ * - Fingerprint verification
11
+ * - Key rotation support
12
+ *
13
+ * SEMANTICS:
14
+ * - Fingerprint = BLAKE2b(identity_public_key)
15
+ * - Binding: identity key ONLY (not bundle, not SPK)
16
+ * - Key rotation: requires manual re-trust via trustNewFingerprint()
17
+ * - Multi-device: NOT supported (each device = new identity)
18
+ */
19
+ interface FingerprintRecord {
20
+ fingerprint: string;
21
+ firstSeen: Date;
22
+ lastSeen: Date;
23
+ version: number;
24
+ trusted: boolean;
25
+ }
26
+ export interface ITofuStore {
27
+ saveFingerprint(userId: string, record: FingerprintRecord): Promise<void>;
28
+ loadFingerprint(userId: string): Promise<FingerprintRecord | null>;
29
+ deleteFingerprint(userId: string): Promise<void>;
30
+ listFingerprints(): Promise<string[]>;
31
+ }
32
+ /**
33
+ * Initialize TOFU manager with optional persistent storage
34
+ */
35
+ export declare function initializeTofu(customStore?: ITofuStore): void;
36
+ /**
37
+ * Generate BLAKE2b-256 fingerprint from identity public key
38
+ *
39
+ * BINDING: Identity key ONLY
40
+ * - SPK rotation does NOT change fingerprint
41
+ * - OPK exhaustion does NOT change fingerprint
42
+ * - Only identity key rotation changes fingerprint
43
+ */
44
+ export declare function generateFingerprint(identityPublicKey: Uint8Array): string;
45
+ /**
46
+ * Store fingerprint for user
47
+ */
48
+ export declare function storeFingerprint(userId: string, fingerprint: string): Promise<void>;
49
+ /**
50
+ * Verify fingerprint against stored value
51
+ *
52
+ * BEHAVIOR:
53
+ * - First use: stores fingerprint, returns true
54
+ * - Match: returns true
55
+ * - Mismatch: throws error (HARD FAILURE)
56
+ */
57
+ export declare function verifyFingerprint(userId: string, identityPublicKey: Uint8Array): Promise<boolean>;
58
+ /**
59
+ * Check if user fingerprint is trusted
60
+ */
61
+ export declare function isFingerprintTrusted(userId: string): Promise<boolean>;
62
+ /**
63
+ * Get fingerprint for user
64
+ */
65
+ export declare function getFingerprint(userId: string): Promise<string | null>;
66
+ /**
67
+ * Revoke trust for a user (after key rotation or suspected compromise)
68
+ */
69
+ export declare function revokeTrust(userId: string): Promise<void>;
70
+ /**
71
+ * Re-trust a user after verifying their new fingerprint out-of-band
72
+ */
73
+ export declare function trustNewFingerprint(userId: string, identityPublicKey: Uint8Array): Promise<void>;
74
+ /**
75
+ * List all trusted fingerprints
76
+ */
77
+ export declare function listTrustedFingerprints(): Promise<string[]>;
78
+ /**
79
+ * Get detailed fingerprint info for debugging
80
+ */
81
+ export declare function getFingerprintInfo(userId: string): Promise<FingerprintRecord | null>;
82
+ export {};
@@ -0,0 +1,166 @@
1
+ /**
2
+ * STVOR TOFU (Trust On First Use) Manager
3
+ * Implements persistent TOFU with fallback to in-memory
4
+ *
5
+ * VERSION 2.0 - PRODUCTION READY
6
+ *
7
+ * Features:
8
+ * - Persistent storage interface
9
+ * - In-memory fallback for development
10
+ * - Fingerprint verification
11
+ * - Key rotation support
12
+ *
13
+ * SEMANTICS:
14
+ * - Fingerprint = BLAKE2b(identity_public_key)
15
+ * - Binding: identity key ONLY (not bundle, not SPK)
16
+ * - Key rotation: requires manual re-trust via trustNewFingerprint()
17
+ * - Multi-device: NOT supported (each device = new identity)
18
+ */
19
+ import { createHash } from 'crypto';
20
+ // In-memory TOFU store (fallback)
21
+ class InMemoryTofuStore {
22
+ constructor() {
23
+ this.store = new Map();
24
+ }
25
+ async saveFingerprint(userId, record) {
26
+ this.store.set(userId, record);
27
+ }
28
+ async loadFingerprint(userId) {
29
+ return this.store.get(userId) || null;
30
+ }
31
+ async deleteFingerprint(userId) {
32
+ this.store.delete(userId);
33
+ }
34
+ async listFingerprints() {
35
+ return Array.from(this.store.keys());
36
+ }
37
+ }
38
+ // Global store instance
39
+ let tofuStore = null;
40
+ const FINGERPRINT_VERSION = 1; // Increment on breaking changes
41
+ /**
42
+ * Initialize TOFU manager with optional persistent storage
43
+ */
44
+ export function initializeTofu(customStore) {
45
+ tofuStore = customStore || new InMemoryTofuStore();
46
+ console.log('[TOFU] Initialized' + (customStore ? ' with persistent storage' : ' with in-memory fallback'));
47
+ }
48
+ /**
49
+ * Generate BLAKE2b-256 fingerprint from identity public key
50
+ *
51
+ * BINDING: Identity key ONLY
52
+ * - SPK rotation does NOT change fingerprint
53
+ * - OPK exhaustion does NOT change fingerprint
54
+ * - Only identity key rotation changes fingerprint
55
+ */
56
+ export function generateFingerprint(identityPublicKey) {
57
+ const hash = createHash('sha256').update(identityPublicKey).digest();
58
+ return hash.toString('hex');
59
+ }
60
+ /**
61
+ * Store fingerprint for user
62
+ */
63
+ export async function storeFingerprint(userId, fingerprint) {
64
+ if (!tofuStore) {
65
+ initializeTofu();
66
+ }
67
+ const record = {
68
+ fingerprint,
69
+ firstSeen: new Date(),
70
+ lastSeen: new Date(),
71
+ version: FINGERPRINT_VERSION,
72
+ trusted: true,
73
+ };
74
+ await tofuStore.saveFingerprint(userId, record);
75
+ console.log(`[TOFU] Stored fingerprint for user: ${userId}`);
76
+ }
77
+ /**
78
+ * Verify fingerprint against stored value
79
+ *
80
+ * BEHAVIOR:
81
+ * - First use: stores fingerprint, returns true
82
+ * - Match: returns true
83
+ * - Mismatch: throws error (HARD FAILURE)
84
+ */
85
+ export async function verifyFingerprint(userId, identityPublicKey) {
86
+ if (!tofuStore) {
87
+ initializeTofu();
88
+ }
89
+ const fingerprint = generateFingerprint(identityPublicKey);
90
+ const stored = await tofuStore.loadFingerprint(userId);
91
+ if (!stored) {
92
+ // First use - store and trust
93
+ await storeFingerprint(userId, fingerprint);
94
+ console.log(`[TOFU] First contact: ${userId} (fingerprint: ${fingerprint.slice(0, 16)}...)`);
95
+ return true;
96
+ }
97
+ if (stored.fingerprint !== fingerprint) {
98
+ // Fingerprint mismatch - potential MITM!
99
+ console.error(`[TOFU] FINGERPRINT MISMATCH for ${userId}!`);
100
+ console.error(`[TOFU] Expected: ${stored.fingerprint.slice(0, 16)}...`);
101
+ console.error(`[TOFU] Received: ${fingerprint.slice(0, 16)}...`);
102
+ throw new Error(`FINGERPRINT MISMATCH for user ${userId} - possible MITM attack!`);
103
+ }
104
+ // Update last seen
105
+ stored.lastSeen = new Date();
106
+ await tofuStore.saveFingerprint(userId, stored);
107
+ return true;
108
+ }
109
+ /**
110
+ * Check if user fingerprint is trusted
111
+ */
112
+ export async function isFingerprintTrusted(userId) {
113
+ if (!tofuStore) {
114
+ initializeTofu();
115
+ }
116
+ const stored = await tofuStore.loadFingerprint(userId);
117
+ return stored?.trusted || false;
118
+ }
119
+ /**
120
+ * Get fingerprint for user
121
+ */
122
+ export async function getFingerprint(userId) {
123
+ if (!tofuStore) {
124
+ initializeTofu();
125
+ }
126
+ const stored = await tofuStore.loadFingerprint(userId);
127
+ return stored?.fingerprint || null;
128
+ }
129
+ /**
130
+ * Revoke trust for a user (after key rotation or suspected compromise)
131
+ */
132
+ export async function revokeTrust(userId) {
133
+ if (!tofuStore) {
134
+ initializeTofu();
135
+ }
136
+ await tofuStore.deleteFingerprint(userId);
137
+ console.log(`[TOFU] Revoked trust for user: ${userId}`);
138
+ }
139
+ /**
140
+ * Re-trust a user after verifying their new fingerprint out-of-band
141
+ */
142
+ export async function trustNewFingerprint(userId, identityPublicKey) {
143
+ const fingerprint = generateFingerprint(identityPublicKey);
144
+ await storeFingerprint(userId, fingerprint);
145
+ console.log(`[TOFU] Re-trusted user: ${userId}`);
146
+ }
147
+ /**
148
+ * List all trusted fingerprints
149
+ */
150
+ export async function listTrustedFingerprints() {
151
+ if (!tofuStore) {
152
+ initializeTofu();
153
+ }
154
+ return tofuStore.listFingerprints();
155
+ }
156
+ /**
157
+ * Get detailed fingerprint info for debugging
158
+ */
159
+ export async function getFingerprintInfo(userId) {
160
+ if (!tofuStore) {
161
+ initializeTofu();
162
+ }
163
+ return tofuStore.loadFingerprint(userId);
164
+ }
165
+ // Default initialization
166
+ initializeTofu();
@@ -47,4 +47,6 @@ export interface SealedPayload {
47
47
  ciphertext: Uint8Array;
48
48
  /** Nonce used for encryption */
49
49
  nonce: Uint8Array;
50
+ /** Recipient user ID this was sealed for */
51
+ recipientId: UserId;
50
52
  }
package/dist/index.d.cts CHANGED
@@ -1,2 +1,6 @@
1
+ /**
2
+ * STVOR SDK - Main exports
3
+ */
1
4
  export * from './legacy.js';
2
5
  export * from './facade/index.js';
6
+ export * from './ratchet/index.js';
package/dist/index.d.ts CHANGED
@@ -1,2 +1,6 @@
1
+ /**
2
+ * STVOR SDK - Main exports
3
+ */
1
4
  export * from './legacy.js';
2
5
  export * from './facade/index.js';
6
+ export * from './ratchet/index.js';
package/dist/index.js CHANGED
@@ -1,2 +1,9 @@
1
+ /**
2
+ * STVOR SDK - Main exports
3
+ */
4
+ // Legacy core API (for migration)
1
5
  export * from './legacy.js';
6
+ // New DX Facade API
2
7
  export * from './facade/index.js';
8
+ // X3DH + Double Ratchet implementation
9
+ export * from './ratchet/index.js';
package/dist/legacy.d.ts CHANGED
@@ -1 +1,31 @@
1
- export {};
1
+ /**
2
+ * STVOR SDK - Legacy Core API
3
+ * Kept for backwards compatibility
4
+ */
5
+ export interface StvorConfig {
6
+ apiKey: string;
7
+ serverUrl?: string;
8
+ }
9
+ export interface Peer {
10
+ id: string;
11
+ publicKey: any;
12
+ }
13
+ export interface EncryptedMessage {
14
+ ciphertext: string;
15
+ nonce: string;
16
+ from: string;
17
+ }
18
+ export declare class StvorClient {
19
+ private config;
20
+ private myKeyPair;
21
+ private myId;
22
+ private peers;
23
+ constructor(config: StvorConfig);
24
+ ready(): Promise<void>;
25
+ createPeer(name: string): Promise<Peer>;
26
+ send({ to, message }: {
27
+ to: string;
28
+ message: string;
29
+ }): Promise<EncryptedMessage>;
30
+ receive(encrypted: EncryptedMessage): Promise<string>;
31
+ }