@xtr-dev/rondevu-client 0.21.3 → 0.21.6

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.
@@ -11,6 +11,7 @@ export { ICE_SERVER_PRESETS } from './ice-config.js';
11
11
  * Rondevu - Complete WebRTC signaling client with durable connections
12
12
  *
13
13
  * Uses a tags-based discovery system where offers have 1+ tags for matching.
14
+ * Authentication uses Ed25519 public key cryptography - your public key IS your identity.
14
15
  *
15
16
  * @example
16
17
  * ```typescript
@@ -56,13 +57,13 @@ export { ICE_SERVER_PRESETS } from './ice-config.js';
56
57
  * ```
57
58
  */
58
59
  export class Rondevu extends EventEmitter {
59
- constructor(apiUrl, credential, api, iceServers, iceTransportPolicy, webrtcAdapter, cryptoAdapter, debugEnabled = false) {
60
+ constructor(apiUrl, keyPair, api, iceServers, iceTransportPolicy, webrtcAdapter, cryptoAdapter, debugEnabled = false) {
60
61
  super();
61
62
  // Publishing state
62
63
  this.currentTags = null;
63
64
  this.offerPool = null;
64
65
  this.apiUrl = apiUrl;
65
- this.credential = credential;
66
+ this.keyPair = keyPair;
66
67
  this.api = api;
67
68
  this.iceServers = iceServers;
68
69
  this.iceTransportPolicy = iceTransportPolicy;
@@ -82,7 +83,7 @@ export class Rondevu extends EventEmitter {
82
83
  this.emit('poll:ice', data);
83
84
  });
84
85
  this.debug('Instance created:', {
85
- name: this.credential.name,
86
+ publicKey: this.keyPair.publicKey,
86
87
  hasIceServers: iceServers.length > 0,
87
88
  iceTransportPolicy: iceTransportPolicy || 'all',
88
89
  });
@@ -116,53 +117,51 @@ export class Rondevu extends EventEmitter {
116
117
  if (options.debug) {
117
118
  console.log('[Rondevu] Connecting:', {
118
119
  apiUrl,
119
- hasCredential: !!options.credential,
120
+ hasKeyPair: !!options.keyPair,
120
121
  iceServers: iceConfig.iceServers?.length ?? 0,
121
122
  iceTransportPolicy: iceConfig.iceTransportPolicy || 'all',
122
123
  });
123
124
  }
124
- // Generate credential if not provided
125
- let credential = options.credential;
126
- if (!credential) {
125
+ // Generate keypair if not provided (purely client-side, no server registration)
126
+ let keyPair = options.keyPair;
127
+ if (!keyPair) {
127
128
  if (options.debug)
128
- console.log('[Rondevu] Generating new credentials...');
129
- credential = await RondevuAPI.generateCredentials(apiUrl, {
130
- name: options.username, // Will claim this username if provided
131
- });
129
+ console.log('[Rondevu] Generating new keypair...');
130
+ keyPair = await RondevuAPI.generateKeyPair(options.cryptoAdapter);
132
131
  if (options.debug)
133
- console.log('[Rondevu] Generated credentials, name:', credential.name);
132
+ console.log('[Rondevu] Generated keypair, public key:', keyPair.publicKey);
134
133
  }
135
134
  else {
136
135
  if (options.debug)
137
- console.log('[Rondevu] Using existing credential, name:', credential.name);
136
+ console.log('[Rondevu] Using existing keypair, public key:', keyPair.publicKey);
138
137
  }
139
138
  // Create API instance
140
- const api = new RondevuAPI(apiUrl, credential, options.cryptoAdapter);
139
+ const api = new RondevuAPI(apiUrl, keyPair, options.cryptoAdapter);
141
140
  if (options.debug)
142
141
  console.log('[Rondevu] Created API instance');
143
- return new Rondevu(apiUrl, credential, api, iceConfig.iceServers || [], iceConfig.iceTransportPolicy, webrtcAdapter, options.cryptoAdapter, options.debug || false);
142
+ return new Rondevu(apiUrl, keyPair, api, iceConfig.iceServers || [], iceConfig.iceTransportPolicy, webrtcAdapter, options.cryptoAdapter, options.debug || false);
144
143
  }
145
144
  // ============================================
146
- // Credential Access
145
+ // Identity Access
147
146
  // ============================================
148
147
  /**
149
- * Get the current credential name
148
+ * Get the current public key (identity)
150
149
  */
151
- getName() {
152
- return this.credential.name;
150
+ getPublicKey() {
151
+ return this.keyPair.publicKey;
153
152
  }
154
153
  /**
155
- * Get the full credential (name + secret)
156
- * Use this to persist credentials for future sessions
154
+ * Get the full keypair (publicKey + privateKey)
155
+ * Use this to persist keypair for future sessions
157
156
  *
158
157
  * ⚠️ SECURITY WARNING:
159
- * - The secret grants full access to this identity
160
- * - Store credentials securely (encrypted storage, never in logs)
161
- * - Never expose credentials in URLs, console output, or error messages
162
- * - Treat the secret like a password or API key
158
+ * - The private key grants full access to this identity
159
+ * - Store keypair securely (encrypted storage, never in logs)
160
+ * - Never expose private key in URLs, console output, or error messages
161
+ * - Treat the private key like a password or API key
163
162
  */
164
- getCredential() {
165
- return { ...this.credential };
163
+ getKeyPair() {
164
+ return { ...this.keyPair };
166
165
  }
167
166
  /**
168
167
  * Get the WebRTC adapter for creating peer connections
@@ -211,7 +210,7 @@ export class Rondevu extends EventEmitter {
211
210
  this.offerPool = new OfferPool({
212
211
  api: this.api,
213
212
  tags,
214
- ownerUsername: this.credential.name,
213
+ ownerPublicKey: this.keyPair.publicKey,
215
214
  maxOffers,
216
215
  offerFactory: offerFactory || this.defaultOfferFactory.bind(this),
217
216
  ttl: ttl || Rondevu.DEFAULT_TTL_MS,
@@ -222,8 +221,8 @@ export class Rondevu extends EventEmitter {
222
221
  debugEnabled: this.debugEnabled,
223
222
  });
224
223
  // Forward events from OfferPool
225
- this.offerPool.on('connection:opened', (offerId, connection) => {
226
- this.emit('connection:opened', offerId, connection);
224
+ this.offerPool.on('connection:opened', (offerId, connection, matchedTags) => {
225
+ this.emit('connection:opened', offerId, connection, matchedTags);
227
226
  });
228
227
  this.offerPool.on('offer:created', (offerId, tags) => {
229
228
  this.emit('offer:created', offerId, tags);
@@ -314,6 +313,17 @@ export class Rondevu extends EventEmitter {
314
313
  this.debug('Disconnecting all offers');
315
314
  this.offerPool?.disconnectAll();
316
315
  }
316
+ /**
317
+ * Update tags for new offers
318
+ * Existing offers keep their old tags until they expire/rotate
319
+ * New offers created during fill will use the updated tags
320
+ * @param newTags - The new tags to use for future offers
321
+ */
322
+ updateOfferTags(newTags) {
323
+ this.debug(`Updating offer tags: ${newTags.join(', ')}`);
324
+ this.currentTags = newTags;
325
+ this.offerPool?.updateTags(newTags);
326
+ }
317
327
  /**
318
328
  * Get the current publishing status
319
329
  * @returns Object with publishing state information
@@ -334,14 +344,14 @@ export class Rondevu extends EventEmitter {
334
344
  * // Connect to any peer matching tags
335
345
  * const peer = await rondevu.peer({ tags: ['chat'] })
336
346
  *
337
- * // Connect to specific user
347
+ * // Connect to specific peer by public key
338
348
  * const peer = await rondevu.peer({
339
- * username: 'alice',
349
+ * publicKey: 'abc123...',
340
350
  * tags: ['chat']
341
351
  * })
342
352
  *
343
353
  * peer.on('open', () => {
344
- * console.log('Connected to', peer.peerUsername)
354
+ * console.log('Connected to', peer.peerPublicKey)
345
355
  * peer.send('Hello!')
346
356
  * })
347
357
  *
@@ -413,7 +423,7 @@ export class Rondevu extends EventEmitter {
413
423
  *
414
424
  * // Access offers
415
425
  * for (const offer of result.offers) {
416
- * console.log(offer.username, offer.tags)
426
+ * console.log(offer.publicKey, offer.tags)
417
427
  * }
418
428
  * ```
419
429
  */
@@ -1,9 +1,13 @@
1
1
  /**
2
2
  * Crypto adapter interface for platform-independent cryptographic operations
3
3
  */
4
- export interface Credential {
5
- name: string;
6
- secret: string;
4
+ /**
5
+ * Ed25519 key pair for identity
6
+ * The public key IS the identity (like Ethereum addresses)
7
+ */
8
+ export interface KeyPair {
9
+ publicKey: string;
10
+ privateKey: string;
7
11
  }
8
12
  /**
9
13
  * Platform-independent crypto adapter interface
@@ -11,25 +15,26 @@ export interface Credential {
11
15
  */
12
16
  export interface CryptoAdapter {
13
17
  /**
14
- * Generate HMAC-SHA256 signature for message authentication
15
- * @param secret - The credential secret (hex string)
18
+ * Generate a new Ed25519 key pair locally
19
+ * The public key serves as the identity
20
+ * @returns Key pair with public and private keys as hex strings
21
+ */
22
+ generateKeyPair(): Promise<KeyPair>;
23
+ /**
24
+ * Sign a message using Ed25519
25
+ * @param privateKey - The private key (64-char hex string)
16
26
  * @param message - The message to sign
17
27
  * @returns Base64-encoded signature
18
28
  */
19
- generateSignature(secret: string, message: string): Promise<string>;
29
+ signMessage(privateKey: string, message: string): Promise<string>;
20
30
  /**
21
- * Verify HMAC-SHA256 signature
22
- * @param secret - The credential secret (hex string)
31
+ * Verify an Ed25519 signature
32
+ * @param publicKey - The public key (64-char hex string)
23
33
  * @param message - The message that was signed
24
34
  * @param signature - The signature to verify (base64)
25
35
  * @returns True if signature is valid
26
36
  */
27
- verifySignature(secret: string, message: string, signature: string): Promise<boolean>;
28
- /**
29
- * Generate a random secret (256-bit hex string)
30
- * @returns 64-character hex string
31
- */
32
- generateSecret(): string;
37
+ verifySignature(publicKey: string, message: string, signature: string): Promise<boolean>;
33
38
  /**
34
39
  * Convert hex string to bytes
35
40
  */
@@ -1,10 +1,11 @@
1
1
  /**
2
2
  * Node.js Crypto adapter for Node.js environments
3
+ * Uses @noble/ed25519 for Ed25519 operations
3
4
  * Requires Node.js 19+ or Node.js 18 with --experimental-global-webcrypto flag
4
5
  */
5
- import { CryptoAdapter } from './adapter.js';
6
+ import { CryptoAdapter, KeyPair } from './adapter.js';
6
7
  /**
7
- * Node.js Crypto implementation using Node.js built-in APIs
8
+ * Node.js Crypto implementation using Node.js built-in APIs and @noble/ed25519
8
9
  * Uses Buffer for base64 encoding and crypto.randomBytes for random generation
9
10
  *
10
11
  * Requirements:
@@ -16,32 +17,30 @@ import { CryptoAdapter } from './adapter.js';
16
17
  * import { RondevuAPI } from '@xtr-dev/rondevu-client'
17
18
  * import { NodeCryptoAdapter } from '@xtr-dev/rondevu-client/node'
18
19
  *
20
+ * const crypto = new NodeCryptoAdapter()
21
+ * const keyPair = await crypto.generateKeyPair()
22
+ *
19
23
  * const api = new RondevuAPI(
20
24
  * 'https://signal.example.com',
21
- * 'alice',
22
- * { name: 'alice', secret: '...' },
23
- * new NodeCryptoAdapter()
25
+ * keyPair,
26
+ * crypto
24
27
  * )
25
28
  * ```
26
29
  */
27
30
  export declare class NodeCryptoAdapter implements CryptoAdapter {
28
31
  constructor();
29
32
  /**
30
- * Generate HMAC-SHA256 signature
33
+ * Generate a new Ed25519 key pair locally
31
34
  */
32
- generateSignature(secret: string, message: string): Promise<string>;
35
+ generateKeyPair(): Promise<KeyPair>;
33
36
  /**
34
- * Verify HMAC-SHA256 signature
35
- * Uses constant-time comparison via Web Crypto API to prevent timing attacks
36
- *
37
- * @returns false for invalid signatures, throws for malformed input
38
- * @throws Error if secret/signature format is invalid (not a verification failure)
37
+ * Sign a message using Ed25519
39
38
  */
40
- verifySignature(secret: string, message: string, signature: string): Promise<boolean>;
39
+ signMessage(privateKey: string, message: string): Promise<string>;
41
40
  /**
42
- * Generate a random secret (256-bit hex string)
41
+ * Verify an Ed25519 signature
43
42
  */
44
- generateSecret(): string;
43
+ verifySignature(publicKey: string, message: string, signature: string): Promise<boolean>;
45
44
  /**
46
45
  * Convert hex string to bytes
47
46
  * @throws Error if hex string is invalid
@@ -1,9 +1,16 @@
1
1
  /**
2
2
  * Node.js Crypto adapter for Node.js environments
3
+ * Uses @noble/ed25519 for Ed25519 operations
3
4
  * Requires Node.js 19+ or Node.js 18 with --experimental-global-webcrypto flag
4
5
  */
6
+ import * as ed from '@noble/ed25519';
7
+ // Configure @noble/ed25519 to use Web Crypto API's SHA-512
8
+ ed.hashes.sha512Async = async (message) => {
9
+ const hashBuffer = await crypto.subtle.digest('SHA-512', message);
10
+ return new Uint8Array(hashBuffer);
11
+ };
5
12
  /**
6
- * Node.js Crypto implementation using Node.js built-in APIs
13
+ * Node.js Crypto implementation using Node.js built-in APIs and @noble/ed25519
7
14
  * Uses Buffer for base64 encoding and crypto.randomBytes for random generation
8
15
  *
9
16
  * Requirements:
@@ -15,11 +22,13 @@
15
22
  * import { RondevuAPI } from '@xtr-dev/rondevu-client'
16
23
  * import { NodeCryptoAdapter } from '@xtr-dev/rondevu-client/node'
17
24
  *
25
+ * const crypto = new NodeCryptoAdapter()
26
+ * const keyPair = await crypto.generateKeyPair()
27
+ *
18
28
  * const api = new RondevuAPI(
19
29
  * 'https://signal.example.com',
20
- * 'alice',
21
- * { name: 'alice', secret: '...' },
22
- * new NodeCryptoAdapter()
30
+ * keyPair,
31
+ * crypto
23
32
  * )
24
33
  * ```
25
34
  */
@@ -31,75 +40,54 @@ export class NodeCryptoAdapter {
31
40
  }
32
41
  }
33
42
  /**
34
- * Generate HMAC-SHA256 signature
43
+ * Generate a new Ed25519 key pair locally
44
+ */
45
+ async generateKeyPair() {
46
+ // Generate 32 random bytes for private key
47
+ const privateKeyBytes = this.randomBytes(32);
48
+ const privateKey = this.bytesToHex(privateKeyBytes);
49
+ // Derive public key from private key
50
+ const publicKeyBytes = await ed.getPublicKeyAsync(privateKeyBytes);
51
+ const publicKey = this.bytesToHex(publicKeyBytes);
52
+ return { publicKey, privateKey };
53
+ }
54
+ /**
55
+ * Sign a message using Ed25519
35
56
  */
36
- async generateSignature(secret, message) {
37
- if (!secret || typeof secret !== 'string') {
38
- throw new Error('Invalid secret: must be a non-empty string');
57
+ async signMessage(privateKey, message) {
58
+ if (!privateKey || typeof privateKey !== 'string') {
59
+ throw new Error('Invalid private key: must be a non-empty string');
39
60
  }
40
61
  if (typeof message !== 'string') {
41
62
  throw new Error('Invalid message: must be a string');
42
63
  }
43
- const secretBytes = this.hexToBytes(secret);
44
- // Import secret as HMAC key
45
- const key = await crypto.subtle.importKey('raw', secretBytes, { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']);
64
+ const privateKeyBytes = this.hexToBytes(privateKey);
46
65
  // Convert message to bytes
47
66
  const encoder = new TextEncoder();
48
67
  const messageBytes = encoder.encode(message);
49
- // Generate HMAC signature
50
- const signatureBytes = await crypto.subtle.sign('HMAC', key, messageBytes);
68
+ // Sign using Ed25519
69
+ const signatureBytes = await ed.signAsync(messageBytes, privateKeyBytes);
51
70
  // Convert to base64
52
- return this.bytesToBase64(new Uint8Array(signatureBytes));
71
+ return this.bytesToBase64(signatureBytes);
53
72
  }
54
73
  /**
55
- * Verify HMAC-SHA256 signature
56
- * Uses constant-time comparison via Web Crypto API to prevent timing attacks
57
- *
58
- * @returns false for invalid signatures, throws for malformed input
59
- * @throws Error if secret/signature format is invalid (not a verification failure)
74
+ * Verify an Ed25519 signature
60
75
  */
61
- async verifySignature(secret, message, signature) {
62
- // Validate inputs first
63
- // Use generic error messages to prevent timing attacks and information leakage
64
- let secretBytes;
65
- let signatureBytes;
66
- try {
67
- secretBytes = this.hexToBytes(secret);
68
- }
69
- catch (error) {
70
- // Generic error message - don't leak format details
71
- throw new Error('Invalid credential format');
72
- }
76
+ async verifySignature(publicKey, message, signature) {
73
77
  try {
74
- signatureBytes = this.base64ToBytes(signature);
75
- }
76
- catch (error) {
77
- // Generic error message - don't leak format details
78
- throw new Error('Invalid signature format');
79
- }
80
- // Perform HMAC verification
81
- try {
82
- // Import secret as HMAC key for verification
83
- const key = await crypto.subtle.importKey('raw', secretBytes, { name: 'HMAC', hash: 'SHA-256' }, false, ['verify']);
78
+ const publicKeyBytes = this.hexToBytes(publicKey);
79
+ const signatureBytes = this.base64ToBytes(signature);
84
80
  // Convert message to bytes
85
81
  const encoder = new TextEncoder();
86
82
  const messageBytes = encoder.encode(message);
87
- // Use Web Crypto API's verify() for constant-time comparison
88
- // Returns false for signature mismatch (auth failure)
89
- return await crypto.subtle.verify('HMAC', key, signatureBytes, messageBytes);
83
+ // Verify using Ed25519
84
+ return await ed.verifyAsync(signatureBytes, messageBytes, publicKeyBytes);
90
85
  }
91
86
  catch (error) {
92
- // System/crypto errors - unexpected failures
93
- throw new Error(`Signature verification error: ${error instanceof Error ? error.message : String(error)}`);
87
+ console.error('Signature verification error:', error);
88
+ return false;
94
89
  }
95
90
  }
96
- /**
97
- * Generate a random secret (256-bit hex string)
98
- */
99
- generateSecret() {
100
- const bytes = this.randomBytes(32); // 32 bytes = 256 bits
101
- return this.bytesToHex(bytes);
102
- }
103
91
  /**
104
92
  * Convert hex string to bytes
105
93
  * @throws Error if hex string is invalid
@@ -1,28 +1,25 @@
1
1
  /**
2
2
  * Web Crypto adapter for browser environments
3
+ * Uses @noble/ed25519 for Ed25519 operations
3
4
  */
4
- import { CryptoAdapter } from './adapter.js';
5
+ import { CryptoAdapter, KeyPair } from './adapter.js';
5
6
  /**
6
- * Web Crypto implementation using browser APIs
7
+ * Web Crypto implementation using browser APIs and @noble/ed25519
7
8
  * Uses btoa/atob for base64 encoding and crypto.getRandomValues for random bytes
8
9
  */
9
10
  export declare class WebCryptoAdapter implements CryptoAdapter {
10
11
  /**
11
- * Generate HMAC-SHA256 signature
12
+ * Generate a new Ed25519 key pair locally
12
13
  */
13
- generateSignature(secret: string, message: string): Promise<string>;
14
+ generateKeyPair(): Promise<KeyPair>;
14
15
  /**
15
- * Verify HMAC-SHA256 signature
16
- * Uses constant-time comparison via Web Crypto API to prevent timing attacks
17
- *
18
- * @returns false for invalid signatures, throws for malformed input
19
- * @throws Error if secret/signature format is invalid (not a verification failure)
16
+ * Sign a message using Ed25519
20
17
  */
21
- verifySignature(secret: string, message: string, signature: string): Promise<boolean>;
18
+ signMessage(privateKey: string, message: string): Promise<string>;
22
19
  /**
23
- * Generate a random secret (256-bit hex string)
20
+ * Verify an Ed25519 signature
24
21
  */
25
- generateSecret(): string;
22
+ verifySignature(publicKey: string, message: string, signature: string): Promise<boolean>;
26
23
  /**
27
24
  * Convert hex string to bytes
28
25
  * @throws Error if hex string is invalid
@@ -1,81 +1,67 @@
1
1
  /**
2
2
  * Web Crypto adapter for browser environments
3
+ * Uses @noble/ed25519 for Ed25519 operations
3
4
  */
5
+ import * as ed from '@noble/ed25519';
6
+ // Configure @noble/ed25519 to use Web Crypto API's SHA-512
7
+ ed.hashes.sha512Async = async (message) => {
8
+ const hashBuffer = await crypto.subtle.digest('SHA-512', message);
9
+ return new Uint8Array(hashBuffer);
10
+ };
4
11
  /**
5
- * Web Crypto implementation using browser APIs
12
+ * Web Crypto implementation using browser APIs and @noble/ed25519
6
13
  * Uses btoa/atob for base64 encoding and crypto.getRandomValues for random bytes
7
14
  */
8
15
  export class WebCryptoAdapter {
9
16
  /**
10
- * Generate HMAC-SHA256 signature
17
+ * Generate a new Ed25519 key pair locally
11
18
  */
12
- async generateSignature(secret, message) {
13
- if (!secret || typeof secret !== 'string') {
14
- throw new Error('Invalid secret: must be a non-empty string');
19
+ async generateKeyPair() {
20
+ // Generate 32 random bytes for private key
21
+ const privateKeyBytes = this.randomBytes(32);
22
+ const privateKey = this.bytesToHex(privateKeyBytes);
23
+ // Derive public key from private key
24
+ const publicKeyBytes = await ed.getPublicKeyAsync(privateKeyBytes);
25
+ const publicKey = this.bytesToHex(publicKeyBytes);
26
+ return { publicKey, privateKey };
27
+ }
28
+ /**
29
+ * Sign a message using Ed25519
30
+ */
31
+ async signMessage(privateKey, message) {
32
+ if (!privateKey || typeof privateKey !== 'string') {
33
+ throw new Error('Invalid private key: must be a non-empty string');
15
34
  }
16
35
  if (typeof message !== 'string') {
17
36
  throw new Error('Invalid message: must be a string');
18
37
  }
19
- const secretBytes = this.hexToBytes(secret);
20
- // Import secret as HMAC key
21
- const key = await crypto.subtle.importKey('raw', secretBytes, { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']);
38
+ const privateKeyBytes = this.hexToBytes(privateKey);
22
39
  // Convert message to bytes
23
40
  const encoder = new TextEncoder();
24
41
  const messageBytes = encoder.encode(message);
25
- // Generate HMAC signature
26
- const signatureBytes = await crypto.subtle.sign('HMAC', key, messageBytes);
42
+ // Sign using Ed25519
43
+ const signatureBytes = await ed.signAsync(messageBytes, privateKeyBytes);
27
44
  // Convert to base64
28
- return this.bytesToBase64(new Uint8Array(signatureBytes));
45
+ return this.bytesToBase64(signatureBytes);
29
46
  }
30
47
  /**
31
- * Verify HMAC-SHA256 signature
32
- * Uses constant-time comparison via Web Crypto API to prevent timing attacks
33
- *
34
- * @returns false for invalid signatures, throws for malformed input
35
- * @throws Error if secret/signature format is invalid (not a verification failure)
48
+ * Verify an Ed25519 signature
36
49
  */
37
- async verifySignature(secret, message, signature) {
38
- // Validate inputs first
39
- // Use generic error messages to prevent timing attacks and information leakage
40
- let secretBytes;
41
- let signatureBytes;
42
- try {
43
- secretBytes = this.hexToBytes(secret);
44
- }
45
- catch (error) {
46
- // Generic error message - don't leak format details
47
- throw new Error('Invalid credential format');
48
- }
50
+ async verifySignature(publicKey, message, signature) {
49
51
  try {
50
- signatureBytes = this.base64ToBytes(signature);
51
- }
52
- catch (error) {
53
- // Generic error message - don't leak format details
54
- throw new Error('Invalid signature format');
55
- }
56
- // Perform HMAC verification
57
- try {
58
- // Import secret as HMAC key for verification
59
- const key = await crypto.subtle.importKey('raw', secretBytes, { name: 'HMAC', hash: 'SHA-256' }, false, ['verify']);
52
+ const publicKeyBytes = this.hexToBytes(publicKey);
53
+ const signatureBytes = this.base64ToBytes(signature);
60
54
  // Convert message to bytes
61
55
  const encoder = new TextEncoder();
62
56
  const messageBytes = encoder.encode(message);
63
- // Use Web Crypto API's verify() for constant-time comparison
64
- // Returns false for signature mismatch (auth failure)
65
- return await crypto.subtle.verify('HMAC', key, signatureBytes, messageBytes);
57
+ // Verify using Ed25519
58
+ return await ed.verifyAsync(signatureBytes, messageBytes, publicKeyBytes);
66
59
  }
67
60
  catch (error) {
68
- // System/crypto errors - unexpected failures
69
- throw new Error(`Signature verification error: ${error instanceof Error ? error.message : String(error)}`);
61
+ console.error('Signature verification error:', error);
62
+ return false;
70
63
  }
71
64
  }
72
- /**
73
- * Generate a random secret (256-bit hex string)
74
- */
75
- generateSecret() {
76
- const bytes = this.randomBytes(32); // 32 bytes = 256 bits
77
- return this.bytesToHex(bytes);
78
- }
79
65
  /**
80
66
  * Convert hex string to bytes
81
67
  * @throws Error if hex string is invalid
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xtr-dev/rondevu-client",
3
- "version": "0.21.3",
3
+ "version": "0.21.6",
4
4
  "description": "TypeScript client for Rondevu with durable WebRTC connections, automatic reconnection, and message queuing",
5
5
  "type": "module",
6
6
  "main": "dist/core/index.js",
@@ -47,6 +47,7 @@
47
47
  "README.md"
48
48
  ],
49
49
  "dependencies": {
50
+ "@noble/ed25519": "^3.0.0",
50
51
  "eventemitter3": "^5.0.1"
51
52
  },
52
53
  "lint-staged": {