@brashkie/signalis-core 0.1.0 → 0.2.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/README.es.md CHANGED
@@ -1,6 +1,6 @@
1
1
  <div align="center">
2
2
 
3
- <img src="https://github.com/Brashkie/signalis-core/blob/main/media/logo.png" alt="Signalis Core" width="200" />
3
+ <img src="media/logo.png" alt="Signalis Core" width="200" />
4
4
 
5
5
  # 🔐 Signalis Core
6
6
 
@@ -13,7 +13,7 @@
13
13
  [![Rust](https://img.shields.io/badge/rust-1.80%2B-orange.svg)](https://www.rust-lang.org/)
14
14
  [![Node](https://img.shields.io/badge/node-%E2%89%A518-339933.svg)](https://nodejs.org/)
15
15
  [![Cobertura](https://img.shields.io/badge/coverage-99%25-brightgreen.svg)](#testing)
16
- [![Tests](https://img.shields.io/badge/tests-100%2B%20pasando-success.svg)](#testing)
16
+ [![Tests](https://img.shields.io/badge/tests-269%2B%20pasando-success.svg)](#testing)
17
17
 
18
18
  [**English**](./README.md) · [**Español**](./README.es.md) · [Documentación](./docs) · [Roadmap](./ROADMAP.md) · [Changelog](./CHANGELOG.md)
19
19
 
@@ -32,6 +32,22 @@ Construida con **Rust** para seguridad y velocidad, expuesta a Node.js mediante
32
32
 
33
33
  ---
34
34
 
35
+ ## 🎉 Novedades en v0.2.0
36
+
37
+ **v0.2.0 introduce firmas digitales y cifrado autenticado con AAD — totalmente retrocompatible con v0.1.0.**
38
+
39
+ | Nuevo | Descripción |
40
+ |-------|-------------|
41
+ | 🆕 **Ed25519** | Firmas digitales estándar (RFC 8032) — determinísticas |
42
+ | 🆕 **XEd25519** | Firmas estilo Signal con llaves Curve25519 — una llave de identidad para ECDH + firmar |
43
+ | 🆕 **AES-GCM con AAD** | Datos Autenticados Adicionales para vincular metadatos al cifrado |
44
+ | 🆕 **SignatureError** | Nueva clase de error tipada para fallos de firma |
45
+ | 🆕 **Tipo Signature** | Tipo branded con helper `asSignature()` |
46
+
47
+ Ver [MIGRATION.md](./MIGRATION.md) para detalles de upgrade (es un drop-in replacement).
48
+
49
+ ---
50
+
35
51
  ## 📋 Tabla de Contenido
36
52
 
37
53
  - [Características](#-características)
@@ -57,14 +73,16 @@ Construida con **Rust** para seguridad y velocidad, expuesta a Node.js mediante
57
73
  | Característica | Descripción |
58
74
  |----------------|-------------|
59
75
  | 🔥 **Velocidad Extrema** | Implementación nativa en Rust vía napi-rs (10-100x más rápido que JS puro) |
60
- | 🛡️ **Crypto Auditada** | Basada en `curve25519-dalek`, suite RustCrypto — librerías probadas en batalla |
76
+ | 🛡️ **Crypto Auditada** | Basada en `curve25519-dalek`, `ed25519-dalek`, suite RustCrypto — librerías probadas en batalla |
77
+ | ✍️ **Firmas Digitales** | Ed25519 (RFC 8032) y XEd25519 (estilo Signal) — **NUEVO v0.2.0** |
78
+ | 🔐 **AEAD con AAD** | AES-256-GCM con Datos Autenticados Adicionales — **NUEVO v0.2.0** |
61
79
  | 📦 **Paquete Dual** | Funciona en proyectos CommonJS, ESM y TypeScript |
62
80
  | 🎯 **Tipado Estricto** | Definiciones TypeScript completas con tipos branded y clases de error |
63
- | ✅ **Vectores de Test** | Validado contra RFC 5869, RFC 7748, RFC 4231 y vectores NIST |
81
+ | ✅ **Vectores de Test** | Validado contra RFC 5869, RFC 7748, RFC 8032, RFC 4231 y vectores NIST |
64
82
  | 🌍 **Multi-Plataforma** | Binarios pre-compilados para Windows, macOS, Linux (x64, ARM) |
65
83
  | 🔒 **Tiempo Constante** | Comparaciones resistentes a side-channel via crate `subtle` |
66
84
  | 🧹 **Auto-Borrado** | Secretos se borran de memoria automáticamente |
67
- | 📊 **Cobertura 99%+** | Suite de tests con 100+ aserciones |
85
+ | 📊 **Cobertura 99%+** | Suite de tests con 269+ aserciones |
68
86
  | 📖 **Bien Documentada** | JSDoc completo + ejemplos inline para cada función |
69
87
 
70
88
  ---
@@ -205,6 +223,86 @@ Curve25519.PUBLIC_KEY_SIZE; // 32
205
223
  Curve25519.SHARED_SECRET_SIZE; // 32
206
224
  ```
207
225
 
226
+ ### Ed25519
227
+
228
+ **NUEVO en v0.2.0.** Firmas digitales Ed25519 estándar (RFC 8032). Determinístico — la misma entrada siempre produce la misma firma.
229
+
230
+ ```typescript
231
+ import { Ed25519, type KeyPair, type Signature } from '@brashkie/signalis-core';
232
+
233
+ // Generar nuevo keypair para firmar
234
+ const keys: KeyPair = Ed25519.generateKeyPair();
235
+ // → { privateKey: Buffer(32), publicKey: Buffer(32) }
236
+
237
+ // Determinístico desde una semilla de 32 bytes
238
+ const fromSeed = Ed25519.keyPairFromSeed(seed);
239
+
240
+ // Derivar pública desde privada
241
+ const pub = Ed25519.publicFromPrivate(privateKey);
242
+
243
+ // Firmar un mensaje → firma de 64 bytes
244
+ const sig: Signature = Ed25519.sign(privateKey, message);
245
+
246
+ // Verificar (lanza SignatureError si falla)
247
+ Ed25519.verify(publicKey, message, sig);
248
+
249
+ // Verificar (retorna boolean, no lanza)
250
+ const ok = Ed25519.verifyBool(publicKey, message, sig);
251
+
252
+ // Constantes
253
+ Ed25519.PRIVATE_KEY_SIZE; // 32
254
+ Ed25519.PUBLIC_KEY_SIZE; // 32
255
+ Ed25519.SIGNATURE_SIZE; // 64
256
+ Ed25519.SEED_SIZE; // 32
257
+ ```
258
+
259
+ ### XEd25519
260
+
261
+ **NUEVO en v0.2.0.** Firmar con el **MISMO** keypair Curve25519 que se usa para ECDH. Esto es lo que el Protocolo Signal usa para las llaves de identidad.
262
+
263
+ ```typescript
264
+ import { Curve25519, XEd25519 } from '@brashkie/signalis-core';
265
+
266
+ // UN solo keypair para ECDH y firmar
267
+ const identidad = Curve25519.generateKeyPair();
268
+
269
+ // Para ECDH:
270
+ const shared = Curve25519.diffieHellman(identidad.privateKey, peerPublic);
271
+
272
+ // La MISMA llave para firmar:
273
+ const sig = XEd25519.sign(identidad.privateKey, message);
274
+
275
+ // Verificar con la MISMA llave pública Curve25519
276
+ XEd25519.verify(identidad.publicKey, message, sig);
277
+
278
+ // Las firmas XEd25519 NO son determinísticas (usan OS RNG)
279
+ const sig1 = XEd25519.sign(identidad.privateKey, message);
280
+ const sig2 = XEd25519.sign(identidad.privateKey, message);
281
+ // sig1.equals(sig2) → false (intencionalmente probabilístico)
282
+
283
+ // Para firmas determinísticas (tests), pasa el random de 64 bytes:
284
+ const random = secureRandom(64);
285
+ const detSig = XEd25519.signWithRandom(identidad.privateKey, message, random);
286
+
287
+ // Verificar (boolean, no lanza)
288
+ const ok = XEd25519.verifyBool(identidad.publicKey, message, sig);
289
+
290
+ // Constantes
291
+ XEd25519.PRIVATE_KEY_SIZE; // 32 (igual que Curve25519)
292
+ XEd25519.PUBLIC_KEY_SIZE; // 32 (igual que Curve25519)
293
+ XEd25519.SIGNATURE_SIZE; // 64
294
+ XEd25519.RANDOM_SIZE; // 64
295
+ ```
296
+
297
+ **¿Cuándo usar Ed25519 vs XEd25519?**
298
+
299
+ | Necesitas | Usa |
300
+ |-----------|-----|
301
+ | Ed25519 estándar, determinístico, compatible con RFC 8032 | **Ed25519** |
302
+ | Una sola llave de identidad para ECDH + firmar (estilo Signal) | **XEd25519** |
303
+ | Firmas reproducibles desde semilla | **Ed25519** |
304
+ | Compatibilidad con semántica del Protocolo Signal | **XEd25519** |
305
+
208
306
  ### HKDF-SHA256
209
307
 
210
308
  Derivación de claves según RFC 5869.
@@ -254,6 +352,26 @@ const pt = AES_GCM.decrypt(clave, nonce, ct);
254
352
  // Lanza AuthenticationError si fue modificado
255
353
  ```
256
354
 
355
+ **Con Datos Autenticados Adicionales (AAD) — NUEVO en v0.2.0:**
356
+
357
+ ```typescript
358
+ import { AES_GCM } from '@brashkie/signalis-core';
359
+
360
+ // AAD se autentica pero NO se cifra — útil para cabeceras/metadatos
361
+ const cabecera = Buffer.from('msg_id=42|emisor=alice');
362
+ const cuerpo = Buffer.from('contenido cifrado del cuerpo');
363
+
364
+ const ct = AES_GCM.encryptWithAad(clave, nonce, cuerpo, cabecera);
365
+
366
+ // Descifrar — DEBE pasar el mismo AAD, o AuthenticationError
367
+ const pt = AES_GCM.decryptWithAad(clave, nonce, ct, cabecera);
368
+
369
+ // Modificar la cabecera (AAD) → falla
370
+ const tampered = Buffer.from(cabecera);
371
+ tampered[0] ^= 0xff;
372
+ AES_GCM.decryptWithAad(clave, nonce, ct, tampered); // lanza AuthenticationError
373
+ ```
374
+
257
375
  > **⚠️ CRÍTICO:** Nunca reutilizar un par `(clave, nonce)`. Usa `randomNonce()` para cada mensaje, o usa un contador determinístico bajo la misma clave (máx 2³² mensajes).
258
376
 
259
377
  ### AES-256-CBC
@@ -335,6 +453,7 @@ import {
335
453
  CryptoError, // Op crypto falló
336
454
  AuthenticationError, // Verificación Tag/MAC falló (extiende CryptoError)
337
455
  KeyDerivationError, // HKDF o similar falló (extiende CryptoError)
456
+ SignatureError, // Verificación Ed25519/XEd25519 falló (extiende CryptoError) — NUEVO v0.2.0
338
457
  LengthError, // Longitud fuera de rango (extiende ValidationError)
339
458
  } from '@brashkie/signalis-core';
340
459
 
@@ -345,6 +464,8 @@ try {
345
464
  console.error('¡Tampering detectado!');
346
465
  } else if (e instanceof ValidationError) {
347
466
  console.error(`Parámetro inválido: ${e.parameter}`);
467
+ } else if (e instanceof SignatureError) {
468
+ console.error('¡Firma inválida!');
348
469
  }
349
470
  }
350
471
  ```
@@ -356,10 +477,13 @@ try {
356
477
  El directorio `examples/` contiene demos completos funcionales:
357
478
 
358
479
  ```bash
359
- npm run example:cjs # CommonJS (10 demos)
360
- npm run example:esm # ESM (canal Alice ↔ Bob)
361
- npm run example:ts # TypeScript (patrones type-safe)
362
- npm run examples # Ejecutar los 3
480
+ npm run example:cjs # CommonJS (10 demos)
481
+ npm run example:esm # ESM (canal Alice ↔ Bob)
482
+ npm run example:ts # TypeScript (patrones type-safe)
483
+ npm run example:signing # Ed25519 + XEd25519 — NUEVO v0.2.0
484
+ npm run example:aad # AES-GCM con AAD — NUEVO v0.2.0
485
+ npm run example:e2e # Canal E2E completo — NUEVO v0.2.0
486
+ npm run examples # Ejecutar todos
363
487
  ```
364
488
 
365
489
  ### Ejemplo: Cifrado seguro de archivos
@@ -409,6 +533,66 @@ function tripleDH(IK_A_priv: Buffer, EK_A_priv: Buffer,
409
533
  }
410
534
  ```
411
535
 
536
+ ### Ejemplo: Firmar aserciones de identidad (NUEVO v0.2.0)
537
+
538
+ ```typescript
539
+ import { Curve25519, XEd25519 } from '@brashkie/signalis-core';
540
+
541
+ // Clave de identidad de largo plazo de Alice (Curve25519/XEd25519)
542
+ const aliceIdentidad = Curve25519.generateKeyPair();
543
+
544
+ // Alice genera una clave efímera para la sesión
545
+ const aliceEfimera = Curve25519.generateKeyPair();
546
+
547
+ // Alice firma su efímera con su identidad — prueba "esta efímera es mía"
548
+ const pruebaAuth = XEd25519.sign(
549
+ aliceIdentidad.privateKey,
550
+ aliceEfimera.publicKey,
551
+ );
552
+
553
+ // Bob verifica la autorización:
554
+ // 1. Tiene la clave pública de identidad de Alice de fuente confiable
555
+ // 2. Recibe la efímera + firma
556
+ try {
557
+ XEd25519.verify(aliceIdentidad.publicKey, aliceEfimera.publicKey, pruebaAuth);
558
+ // ✅ Bob confía que la efímera pertenece a Alice
559
+ } catch {
560
+ // ❌ Mallory intentó MITM con su propia efímera
561
+ }
562
+ ```
563
+
564
+ ### Ejemplo: Mensajes cifrados con cabeceras autenticadas (NUEVO v0.2.0)
565
+
566
+ ```typescript
567
+ import { AES_GCM, secureRandom } from '@brashkie/signalis-core';
568
+
569
+ const claveSesion = derivadaDesdeECDHyHKDF;
570
+
571
+ function enviarMensaje(cuerpo: Buffer, msgId: number) {
572
+ const nonce = secureRandom(12);
573
+ const cabecera = Buffer.from(JSON.stringify({
574
+ msg_id: msgId,
575
+ timestamp: Date.now(),
576
+ emisor: 'alice',
577
+ }));
578
+
579
+ // Cabecera autenticada pero NO cifrada (receptor la necesita en claro)
580
+ const cifrado = AES_GCM.encryptWithAad(claveSesion, nonce, cuerpo, cabecera);
581
+
582
+ return { cabecera, nonce, cifrado };
583
+ }
584
+
585
+ function recibirMensaje(paquete: { cabecera: Buffer; nonce: Buffer; cifrado: Buffer }) {
586
+ // Falla la descifrado si SE MODIFICÓ ciphertext O cabecera
587
+ return AES_GCM.decryptWithAad(
588
+ claveSesion,
589
+ paquete.nonce,
590
+ paquete.cifrado,
591
+ paquete.cabecera,
592
+ );
593
+ }
594
+ ```
595
+
412
596
  ---
413
597
 
414
598
  ## 🏗️ Arquitectura
@@ -416,18 +600,20 @@ function tripleDH(IK_A_priv: Buffer, EK_A_priv: Buffer,
416
600
  ```
417
601
  @brashkie/signalis-core
418
602
 
419
- ├── 🦀 Workspace Rust (5 crates)
603
+ ├── 🦀 Workspace Rust (8 crates)
420
604
  │ ├── sc-curve25519 → Operaciones X25519 ECDH
605
+ │ ├── sc-ed25519 → Firmas Ed25519 (RFC 8032) — NUEVO v0.2.0
606
+ │ ├── sc-xed25519 → Firmas XEd25519 estilo Signal — NUEVO v0.2.0
421
607
  │ ├── sc-hkdf → Derivación HKDF-SHA256
422
- │ ├── sc-aes → AES-256-GCM y CBC
608
+ │ ├── sc-aes → AES-256-GCM (con AAD) y CBC
423
609
  │ ├── sc-hmac → HMAC-SHA256 con verificación tiempo-constante
424
610
  │ ├── sc-sha256 → Hashing SHA-256
425
611
  │ └── sc-node → Bindings NAPI-RS (cdylib)
426
612
 
427
613
  └── 📦 Capa TypeScript
428
614
  ├── core.ts → Wrappers crypto con validación
429
- ├── types.ts → Definiciones de tipos (KeyPair, etc.)
430
- ├── errors.ts → Clases de error tipadas
615
+ ├── types.ts → Definiciones de tipos (KeyPair, Signature, etc.)
616
+ ├── errors.ts → Clases de error tipadas (incl. SignatureError)
431
617
  ├── validators.ts → Aserciones de input
432
618
  ├── utils.ts → Codificación + random + helpers
433
619
  ├── constants.ts → Constantes públicas (tamaños, límites)
@@ -443,8 +629,10 @@ function tripleDH(IK_A_priv: Buffer, EK_A_priv: Buffer,
443
629
  | Primitiva | Especificación | Implementación |
444
630
  |-----------|---------------|----------------|
445
631
  | X25519 | [RFC 7748](https://datatracker.ietf.org/doc/html/rfc7748) | [`curve25519-dalek`](https://github.com/dalek-cryptography/curve25519-dalek) |
632
+ | Ed25519 | [RFC 8032](https://datatracker.ietf.org/doc/html/rfc8032) | [`ed25519-dalek`](https://github.com/dalek-cryptography/ed25519-dalek) — **NUEVO v0.2.0** |
633
+ | XEd25519 | [Signal Spec](https://signal.org/docs/specifications/xeddsa/) | Impl custom sobre `curve25519-dalek` — **NUEVO v0.2.0** |
446
634
  | HKDF-SHA256 | [RFC 5869](https://datatracker.ietf.org/doc/html/rfc5869) | [`hkdf`](https://github.com/RustCrypto/KDFs) |
447
- | AES-256-GCM | [NIST SP 800-38D](https://csrc.nist.gov/publications/detail/sp/800-38d/final) | [`aes-gcm`](https://github.com/RustCrypto/AEADs) |
635
+ | AES-256-GCM | [NIST SP 800-38D](https://csrc.nist.gov/publications/detail/sp/800-38d/final) | [`aes-gcm`](https://github.com/RustCrypto/AEADs) (con soporte AAD) |
448
636
  | AES-256-CBC | [NIST SP 800-38A](https://csrc.nist.gov/publications/detail/sp/800-38a/final) | [`aes`](https://github.com/RustCrypto/block-ciphers) |
449
637
  | HMAC-SHA256 | [RFC 2104](https://datatracker.ietf.org/doc/html/rfc2104) | [`hmac`](https://github.com/RustCrypto/MACs) |
450
638
  | SHA-256 | [FIPS 180-4](https://csrc.nist.gov/publications/detail/fips/180/4/final) | [`sha2`](https://github.com/RustCrypto/hashes) |
@@ -476,8 +664,13 @@ Benchmarks (Node 22, x86_64):
476
664
  |-----------|------------|-------------|
477
665
  | Generación keypair Curve25519 | ~50,000 ops/seg | **15×** más rápido que tweetnacl |
478
666
  | X25519 ECDH | ~25,000 ops/seg | **20×** más rápido |
667
+ | Ed25519 firmar | ~25,000 ops/seg | **20×** más rápido — NUEVO v0.2.0 |
668
+ | Ed25519 verificar | ~10,000 ops/seg | **15×** más rápido — NUEVO v0.2.0 |
669
+ | XEd25519 firmar | ~20,000 ops/seg | — — NUEVO v0.2.0 |
670
+ | XEd25519 verificar | ~10,000 ops/seg | — — NUEVO v0.2.0 |
479
671
  | Derivación HKDF (32 bytes) | ~500,000 ops/seg | **30×** más rápido |
480
672
  | AES-256-GCM cifrado (1 KB) | ~2 GB/seg | **80×** más rápido |
673
+ | AES-GCM con AAD | <5% overhead vs sin AAD | — NUEVO v0.2.0 |
481
674
  | SHA-256 (1 KB) | ~3 GB/seg | **50×** más rápido |
482
675
  | HMAC-SHA256 (1 KB) | ~2.5 GB/seg | **40×** más rápido |
483
676
 
@@ -547,9 +740,10 @@ npm run examples
547
740
  Ver [ROADMAP.md](./ROADMAP.md) para detalles.
548
741
 
549
742
  **TL;DR:**
550
- - **v0.1** ✅ — Primitivas criptográficas (actual)
551
- - **v0.2** — Benchmarks, firmas Ed25519, soporte X448
552
- - **v1.0** — API estable, auditoría
743
+ - **v0.1** ✅ — Primitivas criptográficas (Curve25519, HKDF, AES, HMAC, SHA-256)
744
+ - **v0.2** Ed25519, XEd25519, AES-GCM con AAD (release actual)
745
+ - **v0.3** — Benchmarks, soporte X448, más vectores de test
746
+ - **v1.0** — API estable, auditoría externa
553
747
  - **Luego:** [@brashkie/signalis](https://github.com/Brashkie/signalis) (X3DH + Double Ratchet)
554
748
  - **Luego:** [@brashkie/waproto](https://github.com/Brashkie/waproto) (Protocolo WhatsApp)
555
749
  - **Luego:** HepeinBaileys 2.0 (cliente WhatsApp completo desde cero)
@@ -574,9 +768,10 @@ Por favor también lee nuestro [Código de Conducta](./CODE_OF_CONDUCT.md).
574
768
  Construido sobre los hombros de gigantes:
575
769
 
576
770
  - **[curve25519-dalek](https://github.com/dalek-cryptography/curve25519-dalek)** — Curve25519 en Rust puro
771
+ - **[ed25519-dalek](https://github.com/dalek-cryptography/ed25519-dalek)** — Ed25519 en Rust puro (agregado en v0.2.0)
577
772
  - **[RustCrypto](https://github.com/RustCrypto)** — `aes`, `hkdf`, `hmac`, `sha2`
578
773
  - **[napi-rs](https://napi.rs)** — Bindings Rust ↔ Node
579
- - **Signal Foundation** — [Especificaciones del protocolo](https://signal.org/docs/)
774
+ - **Signal Foundation** — [Especificaciones del protocolo](https://signal.org/docs/) (incluyendo [XEd25519](https://signal.org/docs/specifications/xeddsa/))
580
775
  - **[tsup](https://tsup.egoist.dev/)** — Bundler dual ESM/CJS
581
776
  - **[Vitest](https://vitest.dev/)** — Test runner moderno
582
777
 
@@ -596,4 +791,4 @@ Ver [LICENSE](./LICENSE) y [NOTICE](./NOTICE) para detalles completos.
596
791
 
597
792
  [GitHub](https://github.com/Brashkie) · [npm](https://www.npmjs.com/~brashkie)
598
793
 
599
- </div>
794
+ </div>