@brashkie/signalis-core 0.1.0 β†’ 0.3.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.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
  [![Coverage](https://img.shields.io/badge/coverage-99%25-brightgreen.svg)](#testing)
16
- [![Tests](https://img.shields.io/badge/tests-100%2B%20passing-success.svg)](#testing)
16
+ [![Tests](https://img.shields.io/badge/tests-269%2B%20passing-success.svg)](#testing)
17
17
 
18
18
  [**English**](./README.md) Β· [**EspaΓ±ol**](./README.es.md) Β· [Docs](./docs) Β· [Roadmap](./ROADMAP.md) Β· [Changelog](./CHANGELOG.md)
19
19
 
@@ -32,10 +32,96 @@ Built with **Rust** for safety and speed, exposed to Node.js via [napi-rs](https
32
32
 
33
33
  ---
34
34
 
35
+ ## πŸŽ‰ What's New in v0.3.0
36
+
37
+ **v0.3.0 ships Android support + ChaCha20-Poly1305 β€” fully backwards compatible with v0.2.0.**
38
+
39
+ | New | Description |
40
+ |-----|-------------|
41
+ | πŸ†• **Android arm64-v8a** | Native binary for modern Android phones (React Native, Termux) |
42
+ | πŸ†• **Android armv7** | Native binary for older Android devices (Android 4.4+) |
43
+ | πŸ†• **ChaCha20-Poly1305** | RFC 8439 AEAD β€” 2-3Γ— faster than AES-GCM on ARM without AES-NI |
44
+ | πŸ†• **`constantTimeEq()`** | Timing-safe Buffer comparison (for MAC/signature checking) |
45
+ | πŸ†• **`nativeSecureRandom()`** | OS-backed CSPRNG via Rust side (alternative to JS `secureRandom`) |
46
+ | πŸ†• **`sc-utils` crate** | Public utility helpers (random, constant_time_eq, secure_zeroize) |
47
+ | πŸ†• **CI: cargo-audit gate** | Vulnerable transitive dependencies now fail PRs automatically |
48
+
49
+ ### Quick Example: ChaCha20-Poly1305
50
+
51
+ ```ts
52
+ import { ChaCha20Poly1305, secureRandom } from '@brashkie/signalis-core';
53
+
54
+ const key = secureRandom(32);
55
+ const nonce = secureRandom(12);
56
+
57
+ const ct = ChaCha20Poly1305.encrypt(key, nonce, Buffer.from('Hello!'));
58
+ const pt = ChaCha20Poly1305.decrypt(key, nonce, ct); // β†’ "Hello!"
59
+
60
+ // With Additional Authenticated Data
61
+ const aad = Buffer.from('header metadata');
62
+ const ct2 = ChaCha20Poly1305.encryptWithAad(key, nonce, plaintext, aad);
63
+ const pt2 = ChaCha20Poly1305.decryptWithAad(key, nonce, ct2, aad);
64
+ ```
65
+
66
+ ### Why Should I Use ChaCha20-Poly1305 Instead of AES-GCM?
67
+
68
+ Use **AES-GCM** on:
69
+ - x86_64 servers (Intel/AMD with AES-NI hardware)
70
+ - Modern desktops
71
+ - Newer ARM CPUs with ARMv8 Crypto Extensions
72
+
73
+ Use **ChaCha20-Poly1305** on:
74
+ - Android phones (most don't have AES-NI)
75
+ - Embedded / IoT devices
76
+ - Anything without hardware AES support
77
+
78
+ Both have **identical security guarantees**. The difference is purely performance, depending on whether your target has AES-NI hardware.
79
+
80
+ See [MIGRATION.md](./MIGRATION.md) for upgrade details (it's a drop-in replacement).
81
+
82
+ ---
83
+
84
+ ## 🌍 Supported Platforms
85
+
86
+ `@brashkie/signalis-core` ships prebuilt native binaries for **9 platforms** via npm `optionalDependencies`. The right binary downloads automatically based on your host OS + arch.
87
+
88
+ | OS | Architecture | Sub-package | Status |
89
+ |----|--------------|-------------|--------|
90
+ | 🐧 Linux | x64 (glibc) | `signalis-core-linux-x64-gnu` | βœ… |
91
+ | 🐧 Linux | x64 (musl) | `signalis-core-linux-x64-musl` | βœ… |
92
+ | 🐧 Linux | arm64 (glibc) | `signalis-core-linux-arm64-gnu` | βœ… |
93
+ | 🍎 macOS | x64 (Intel) | `signalis-core-darwin-x64` | βœ… |
94
+ | 🍎 macOS | arm64 (Apple Silicon) | `signalis-core-darwin-arm64` | βœ… |
95
+ | πŸͺŸ Windows | x64 | `signalis-core-win32-x64-msvc` | βœ… |
96
+ | πŸͺŸ Windows | arm64 | `signalis-core-win32-arm64-msvc` | βœ… |
97
+ | πŸ€– Android | arm64-v8a | `signalis-core-android-arm64` | πŸ†• v0.3.0 |
98
+ | πŸ€– Android | armv7 | `signalis-core-android-arm-eabi` | πŸ†• v0.3.0 |
99
+
100
+ Coming in v0.4.0: **iOS arm64**, **WASM (browsers)**, **FreeBSD x64**.
101
+
102
+ ### Android Installation
103
+
104
+ Same `npm install` works in any Node.js environment running on Android, including:
105
+ - **Termux** on Android phones (`pkg install nodejs`)
106
+ - **React Native** with Android target
107
+ - **NodeJS-Mobile** apps
108
+ - Custom embedded Node builds for IoT
109
+
110
+ ```bash
111
+ # On Android (Termux for example):
112
+ pkg install nodejs
113
+ npm install @brashkie/signalis-core
114
+ # β†’ npm automatically downloads signalis-core-android-arm64 sub-package
115
+ ```
116
+
117
+ ---
118
+
35
119
  ## πŸ“‹ Table of Contents
36
120
 
37
121
  - [πŸ” Signalis Core](#-signalis-core)
38
122
  - [✨ What is Signalis Core?](#-what-is-signalis-core)
123
+ - [πŸŽ‰ What's New in v0.3.0](#-whats-new-in-v030)
124
+ - [🌍 Supported Platforms](#-supported-platforms)
39
125
  - [πŸ“‹ Table of Contents](#-table-of-contents)
40
126
  - [πŸš€ Features](#-features)
41
127
  - [πŸ€” Why Signalis Core?](#-why-signalis-core)
@@ -49,6 +135,8 @@ Built with **Rust** for safety and speed, exposed to Node.js via [napi-rs](https
49
135
  - [Default Import](#default-import)
50
136
  - [πŸ“š API Reference](#-api-reference)
51
137
  - [Curve25519 / X25519](#curve25519--x25519)
138
+ - [Ed25519](#ed25519)
139
+ - [XEd25519](#xed25519)
52
140
  - [HKDF-SHA256](#hkdf-sha256)
53
141
  - [AES-256-GCM](#aes-256-gcm)
54
142
  - [AES-256-CBC](#aes-256-cbc)
@@ -59,6 +147,8 @@ Built with **Rust** for safety and speed, exposed to Node.js via [napi-rs](https
59
147
  - [πŸ’‘ Examples](#-examples)
60
148
  - [Example: Secure file encryption](#example-secure-file-encryption)
61
149
  - [Example: Signal-style "Triple DH"](#example-signal-style-triple-dh)
150
+ - [Example: Signing identity assertions (NEW v0.2.0)](#example-signing-identity-assertions-new-v020)
151
+ - [Example: Encrypted messages with authenticated headers (NEW v0.2.0)](#example-encrypted-messages-with-authenticated-headers-new-v020)
62
152
  - [πŸ—οΈ Architecture](#️-architecture)
63
153
  - [Build Output](#build-output)
64
154
  - [πŸ›‘οΈ Security](#️-security)
@@ -82,14 +172,16 @@ Built with **Rust** for safety and speed, exposed to Node.js via [napi-rs](https
82
172
  | Feature | Description |
83
173
  |---------|-------------|
84
174
  | πŸ”₯ **Blazing Fast** | Native Rust implementation via napi-rs (10-100x faster than pure JS) |
85
- | πŸ›‘οΈ **Audited Crypto** | Built on `curve25519-dalek`, RustCrypto suite β€” battle-tested libraries |
175
+ | πŸ›‘οΈ **Audited Crypto** | Built on `curve25519-dalek`, `ed25519-dalek`, RustCrypto suite β€” battle-tested libraries |
176
+ | ✍️ **Digital Signatures** | Ed25519 (RFC 8032) and XEd25519 (Signal-style) β€” **NEW v0.2.0** |
177
+ | πŸ” **AEAD with AAD** | AES-256-GCM with Additional Authenticated Data β€” **NEW v0.2.0** |
86
178
  | πŸ“¦ **Dual Package** | Works in CommonJS, ESM, and TypeScript projects |
87
179
  | 🎯 **Type-Safe** | Full TypeScript definitions with branded types and rich error classes |
88
- | βœ… **Test Vectors** | Validated against RFC 5869, RFC 7748, RFC 4231, and NIST vectors |
89
- | 🌍 **Cross-Platform** | Prebuilt binaries for Windows, macOS, Linux (x64, ARM) |
180
+ | βœ… **Test Vectors** | Validated against RFC 5869, RFC 7748, RFC 8032, RFC 4231, and NIST vectors |
181
+ | 🌍 **Cross-Platform** | Prebuilt binaries for Windows, macOS, Linux (x64, ARM) + **Android arm64/armv7** |
90
182
  | πŸ”’ **Constant-Time** | Side-channel resistant comparisons via `subtle` crate |
91
183
  | 🧹 **Auto-Zeroization** | Secrets are wiped from memory automatically |
92
- | πŸ“Š **99%+ Coverage** | Comprehensive test suite with 100+ assertions |
184
+ | πŸ“Š **99%+ Coverage** | Comprehensive test suite with 269+ assertions |
93
185
  | πŸ“– **Well Documented** | Complete JSDoc + inline examples for every function |
94
186
 
95
187
  ---
@@ -234,6 +326,86 @@ Curve25519.PUBLIC_KEY_SIZE; // 32
234
326
  Curve25519.SHARED_SECRET_SIZE; // 32
235
327
  ```
236
328
 
329
+ ### Ed25519
330
+
331
+ **NEW in v0.2.0.** Standard Ed25519 digital signatures (RFC 8032). Deterministic β€” same input always produces the same signature.
332
+
333
+ ```typescript
334
+ import { Ed25519, type KeyPair, type Signature } from '@brashkie/signalis-core';
335
+
336
+ // Generate a new signing keypair
337
+ const keys: KeyPair = Ed25519.generateKeyPair();
338
+ // β†’ { privateKey: Buffer(32), publicKey: Buffer(32) }
339
+
340
+ // Deterministic from a 32-byte seed
341
+ const fromSeed = Ed25519.keyPairFromSeed(seed);
342
+
343
+ // Derive public from private
344
+ const pub = Ed25519.publicFromPrivate(privateKey);
345
+
346
+ // Sign a message β†’ 64-byte signature
347
+ const sig: Signature = Ed25519.sign(privateKey, message);
348
+
349
+ // Verify (throws SignatureError on failure)
350
+ Ed25519.verify(publicKey, message, sig);
351
+
352
+ // Verify (returns boolean, no throw)
353
+ const ok = Ed25519.verifyBool(publicKey, message, sig);
354
+
355
+ // Constants
356
+ Ed25519.PRIVATE_KEY_SIZE; // 32
357
+ Ed25519.PUBLIC_KEY_SIZE; // 32
358
+ Ed25519.SIGNATURE_SIZE; // 64
359
+ Ed25519.SEED_SIZE; // 32
360
+ ```
361
+
362
+ ### XEd25519
363
+
364
+ **NEW in v0.2.0.** Sign with the **SAME** Curve25519 keypair used for ECDH. This is what the Signal Protocol uses for identity keys.
365
+
366
+ ```typescript
367
+ import { Curve25519, XEd25519 } from '@brashkie/signalis-core';
368
+
369
+ // ONE keypair for both ECDH and signing
370
+ const identity = Curve25519.generateKeyPair();
371
+
372
+ // Use for ECDH:
373
+ const shared = Curve25519.diffieHellman(identity.privateKey, peerPublic);
374
+
375
+ // Use the SAME key to sign:
376
+ const sig = XEd25519.sign(identity.privateKey, message);
377
+
378
+ // Verify with the SAME Curve25519 public key
379
+ XEd25519.verify(identity.publicKey, message, sig);
380
+
381
+ // XEd25519 signatures are NOT deterministic (use OS RNG)
382
+ const sig1 = XEd25519.sign(identity.privateKey, message);
383
+ const sig2 = XEd25519.sign(identity.privateKey, message);
384
+ // sig1.equals(sig2) β†’ false (intentionally probabilistic)
385
+
386
+ // For deterministic signing (testing), provide explicit 64-byte random:
387
+ const random = secureRandom(64);
388
+ const detSig = XEd25519.signWithRandom(identity.privateKey, message, random);
389
+
390
+ // Verify (boolean, no throw)
391
+ const ok = XEd25519.verifyBool(identity.publicKey, message, sig);
392
+
393
+ // Constants
394
+ XEd25519.PRIVATE_KEY_SIZE; // 32 (same as Curve25519)
395
+ XEd25519.PUBLIC_KEY_SIZE; // 32 (same as Curve25519)
396
+ XEd25519.SIGNATURE_SIZE; // 64
397
+ XEd25519.RANDOM_SIZE; // 64
398
+ ```
399
+
400
+ **When to use Ed25519 vs XEd25519:**
401
+
402
+ | Need | Use |
403
+ |------|-----|
404
+ | Standard Ed25519, deterministic, RFC 8032 compliant | **Ed25519** |
405
+ | Single identity key for ECDH + signing (Signal style) | **XEd25519** |
406
+ | Reproducible signatures from seed | **Ed25519** |
407
+ | Compatibility with Signal Protocol semantics | **XEd25519** |
408
+
237
409
  ### HKDF-SHA256
238
410
 
239
411
  Key derivation per RFC 5869.
@@ -283,6 +455,26 @@ const pt = AES_GCM.decrypt(key, nonce, ct);
283
455
  // Throws AuthenticationError if tampered
284
456
  ```
285
457
 
458
+ **With Additional Authenticated Data (AAD) β€” NEW in v0.2.0:**
459
+
460
+ ```typescript
461
+ import { AES_GCM } from '@brashkie/signalis-core';
462
+
463
+ // AAD is authenticated but NOT encrypted β€” useful for headers/metadata
464
+ const header = Buffer.from('msg_id=42|sender=alice');
465
+ const body = Buffer.from('encrypted body content');
466
+
467
+ const ct = AES_GCM.encryptWithAad(key, nonce, body, header);
468
+
469
+ // Decrypt β€” MUST pass same AAD, or AuthenticationError
470
+ const pt = AES_GCM.decryptWithAad(key, nonce, ct, header);
471
+
472
+ // Tampering with header (AAD) β†’ fails
473
+ const tampered = Buffer.from(header);
474
+ tampered[0] ^= 0xff;
475
+ AES_GCM.decryptWithAad(key, nonce, ct, tampered); // throws AuthenticationError
476
+ ```
477
+
286
478
  > **⚠️ CRITICAL:** Never reuse a `(key, nonce)` pair. Use `randomNonce()` for every message, or use a deterministic counter under the same key (max 2Β³Β² messages).
287
479
 
288
480
  ### AES-256-CBC
@@ -303,6 +495,43 @@ if (HMAC.verifySha256(macKey, concat([iv, ct]), tag)) {
303
495
  }
304
496
  ```
305
497
 
498
+ ### ChaCha20-Poly1305 πŸ†•
499
+
500
+ RFC 8439 authenticated encryption. Alternative to AES-GCM, faster on ARM without AES-NI.
501
+
502
+ ```typescript
503
+ import { ChaCha20Poly1305, secureRandom } from '@brashkie/signalis-core';
504
+
505
+ const key = secureRandom(32); // ChaCha20Poly1305.KEY_SIZE
506
+ const nonce = secureRandom(12); // ChaCha20Poly1305.NONCE_SIZE
507
+
508
+ // Basic encrypt/decrypt (no AAD)
509
+ const ct = ChaCha20Poly1305.encrypt(key, nonce, plaintext);
510
+ const pt = ChaCha20Poly1305.decrypt(key, nonce, ct);
511
+
512
+ // With Additional Authenticated Data
513
+ const aad = Buffer.from('public header');
514
+ const ct2 = ChaCha20Poly1305.encryptWithAad(key, nonce, plaintext, aad);
515
+ const pt2 = ChaCha20Poly1305.decryptWithAad(key, nonce, ct2, aad);
516
+
517
+ // Constants
518
+ ChaCha20Poly1305.KEY_SIZE; // 32
519
+ ChaCha20Poly1305.NONCE_SIZE; // 12
520
+ ChaCha20Poly1305.TAG_SIZE; // 16 (appended to ciphertext)
521
+ ```
522
+
523
+ **Performance vs AES-GCM:**
524
+
525
+ | Target | Winner | Why |
526
+ |--------|--------|-----|
527
+ | Server x86_64 (AES-NI) | AES-GCM | Hardware acceleration |
528
+ | Modern desktop (AES-NI) | AES-GCM | Hardware acceleration |
529
+ | Android arm64 (no ARMv8 crypto) | **ChaCha20-Poly1305** | Pure CPU, ChaCha is faster |
530
+ | IoT / embedded | **ChaCha20-Poly1305** | No AES hardware needed |
531
+ | Apple Silicon (M-series) | Either (close) | Both well-optimized |
532
+
533
+ **Security:** ChaCha20-Poly1305 and AES-GCM have equivalent security properties. The choice is purely about performance on your target.
534
+
306
535
  ### HMAC-SHA256
307
536
 
308
537
  Message authentication.
@@ -344,7 +573,9 @@ import {
344
573
  fromBase64Url,
345
574
 
346
575
  // Security
347
- constantTimeEqual, // (a, b) β†’ boolean (timing-safe)
576
+ constantTimeEqual, // (a, b) β†’ boolean (timing-safe, JS-side)
577
+ constantTimeEq, // πŸ†• v0.3.0 β€” same but routed via Rust side
578
+ nativeSecureRandom, // πŸ†• v0.3.0 β€” OS CSPRNG via Rust (vs node:crypto)
348
579
 
349
580
  // Buffer ops
350
581
  concat, // (buffers[]) β†’ Buffer
@@ -353,6 +584,14 @@ import {
353
584
  } from '@brashkie/signalis-core';
354
585
  ```
355
586
 
587
+ #### When to use `constantTimeEq` vs `constantTimeEqual`?
588
+
589
+ Both compare in constant time. `constantTimeEqual` uses JavaScript's `Buffer.compare` semantics (the JS-side implementation). `constantTimeEq` (v0.3.0) routes through Rust's audited `subtle::ConstantTimeEq` crate. Either is safe β€” use `constantTimeEq` if you want to ensure your timing-safe code path matches what the rest of the library uses internally.
590
+
591
+ #### When to use `nativeSecureRandom` vs `secureRandom`?
592
+
593
+ `secureRandom(n)` uses Node's `crypto.randomBytes()`. `nativeSecureRandom(n)` (v0.3.0) routes through Rust's `OsRng` (which calls `getrandom`/`BCryptGenRandom`/`SecRandomCopyBytes` directly). Both are cryptographically secure. Use `nativeSecureRandom` if you want a single audit surface for entropy across your whole stack.
594
+
356
595
  ### Errors
357
596
 
358
597
  All errors extend `SignalisError`:
@@ -364,6 +603,7 @@ import {
364
603
  CryptoError, // Crypto op failed
365
604
  AuthenticationError, // Tag/MAC verification failed (extends CryptoError)
366
605
  KeyDerivationError, // HKDF or similar failed (extends CryptoError)
606
+ SignatureError, // Ed25519/XEd25519 verify failed (extends CryptoError) β€” NEW v0.2.0
367
607
  LengthError, // Output length out of bounds (extends ValidationError)
368
608
  } from '@brashkie/signalis-core';
369
609
 
@@ -374,6 +614,8 @@ try {
374
614
  console.error('Tampering detected!');
375
615
  } else if (e instanceof ValidationError) {
376
616
  console.error(`Invalid parameter: ${e.parameter}`);
617
+ } else if (e instanceof SignatureError) {
618
+ console.error('Invalid signature!');
377
619
  }
378
620
  }
379
621
  ```
@@ -385,10 +627,13 @@ try {
385
627
  The `examples/` directory contains complete working demos:
386
628
 
387
629
  ```bash
388
- npm run example:cjs # CommonJS (10 demos)
389
- npm run example:esm # ESM (Alice ↔ Bob channel)
390
- npm run example:ts # TypeScript (type-safe patterns)
391
- npm run examples # Run all three
630
+ npm run example:cjs # CommonJS (10 demos)
631
+ npm run example:esm # ESM (Alice ↔ Bob channel)
632
+ npm run example:ts # TypeScript (type-safe patterns)
633
+ npm run example:signing # Ed25519 + XEd25519 β€” NEW v0.2.0
634
+ npm run example:aad # AES-GCM with AAD β€” NEW v0.2.0
635
+ npm run example:e2e # Complete E2E channel β€” NEW v0.2.0
636
+ npm run examples # Run all
392
637
  ```
393
638
 
394
639
  ### Example: Secure file encryption
@@ -438,6 +683,66 @@ function tripleDH(IK_A_priv: Buffer, EK_A_priv: Buffer,
438
683
  }
439
684
  ```
440
685
 
686
+ ### Example: Signing identity assertions (NEW v0.2.0)
687
+
688
+ ```typescript
689
+ import { Curve25519, XEd25519 } from '@brashkie/signalis-core';
690
+
691
+ // Alice's long-term identity key (Curve25519/XEd25519)
692
+ const aliceIdentity = Curve25519.generateKeyPair();
693
+
694
+ // Alice generates an ephemeral session key
695
+ const aliceEphemeral = Curve25519.generateKeyPair();
696
+
697
+ // Alice signs her ephemeral with her identity β€” proves "this ephemeral is mine"
698
+ const authProof = XEd25519.sign(
699
+ aliceIdentity.privateKey,
700
+ aliceEphemeral.publicKey,
701
+ );
702
+
703
+ // Bob verifies the authorization:
704
+ // 1. Got Alice's identity public key from a trusted source
705
+ // 2. Receives ephemeral + signature
706
+ try {
707
+ XEd25519.verify(aliceIdentity.publicKey, aliceEphemeral.publicKey, authProof);
708
+ // βœ… Bob now trusts the ephemeral key belongs to Alice
709
+ } catch {
710
+ // ❌ Mallory tried to MITM with her own ephemeral
711
+ }
712
+ ```
713
+
714
+ ### Example: Encrypted messages with authenticated headers (NEW v0.2.0)
715
+
716
+ ```typescript
717
+ import { AES_GCM, secureRandom } from '@brashkie/signalis-core';
718
+
719
+ const sessionKey = derivedFromECDHandHKDF;
720
+
721
+ function sendMessage(body: Buffer, msgId: number) {
722
+ const nonce = secureRandom(12);
723
+ const header = Buffer.from(JSON.stringify({
724
+ msg_id: msgId,
725
+ timestamp: Date.now(),
726
+ sender: 'alice',
727
+ }));
728
+
729
+ // Header is authenticated but NOT encrypted (receiver needs it as plaintext)
730
+ const ciphertext = AES_GCM.encryptWithAad(sessionKey, nonce, body, header);
731
+
732
+ return { header, nonce, ciphertext };
733
+ }
734
+
735
+ function receiveMessage(packet: { header: Buffer; nonce: Buffer; ciphertext: Buffer }) {
736
+ // Decryption fails if EITHER ciphertext OR header was tampered
737
+ return AES_GCM.decryptWithAad(
738
+ sessionKey,
739
+ packet.nonce,
740
+ packet.ciphertext,
741
+ packet.header,
742
+ );
743
+ }
744
+ ```
745
+
441
746
  ---
442
747
 
443
748
  ## πŸ—οΈ Architecture
@@ -445,18 +750,20 @@ function tripleDH(IK_A_priv: Buffer, EK_A_priv: Buffer,
445
750
  ```
446
751
  @brashkie/signalis-core
447
752
  β”‚
448
- β”œβ”€β”€ πŸ¦€ Rust Workspace (5 crates)
753
+ β”œβ”€β”€ πŸ¦€ Rust Workspace (8 crates)
449
754
  β”‚ β”œβ”€β”€ sc-curve25519 β†’ X25519 ECDH operations
755
+ β”‚ β”œβ”€β”€ sc-ed25519 β†’ Ed25519 signatures (RFC 8032) β€” NEW v0.2.0
756
+ β”‚ β”œβ”€β”€ sc-xed25519 β†’ XEd25519 Signal-style signatures β€” NEW v0.2.0
450
757
  β”‚ β”œβ”€β”€ sc-hkdf β†’ HKDF-SHA256 derivation
451
- β”‚ β”œβ”€β”€ sc-aes β†’ AES-256-GCM & CBC
758
+ β”‚ β”œβ”€β”€ sc-aes β†’ AES-256-GCM (with AAD) & CBC
452
759
  β”‚ β”œβ”€β”€ sc-hmac β†’ HMAC-SHA256 with constant-time verify
453
760
  β”‚ β”œβ”€β”€ sc-sha256 β†’ SHA-256 hashing
454
761
  β”‚ └── sc-node β†’ NAPI-RS bindings (cdylib)
455
762
  β”‚
456
763
  └── πŸ“¦ TypeScript Layer
457
764
  β”œβ”€β”€ core.ts β†’ Crypto wrappers with validation
458
- β”œβ”€β”€ types.ts β†’ Type definitions (KeyPair, etc.)
459
- β”œβ”€β”€ errors.ts β†’ Typed error classes
765
+ β”œβ”€β”€ types.ts β†’ Type definitions (KeyPair, Signature, etc.)
766
+ β”œβ”€β”€ errors.ts β†’ Typed error classes (incl. SignatureError)
460
767
  β”œβ”€β”€ validators.ts β†’ Input assertions
461
768
  β”œβ”€β”€ utils.ts β†’ Encoding + random + buffer helpers
462
769
  β”œβ”€β”€ constants.ts β†’ Public constants (sizes, limits)
@@ -487,8 +794,10 @@ dist/
487
794
  | Primitive | Spec | Implementation |
488
795
  |-----------|------|----------------|
489
796
  | X25519 | [RFC 7748](https://datatracker.ietf.org/doc/html/rfc7748) | [`curve25519-dalek`](https://github.com/dalek-cryptography/curve25519-dalek) (audited by NCC Group) |
797
+ | Ed25519 | [RFC 8032](https://datatracker.ietf.org/doc/html/rfc8032) | [`ed25519-dalek`](https://github.com/dalek-cryptography/ed25519-dalek) (audited) β€” **NEW v0.2.0** |
798
+ | XEd25519 | [Signal Spec](https://signal.org/docs/specifications/xeddsa/) | Custom impl over `curve25519-dalek` β€” **NEW v0.2.0** |
490
799
  | HKDF-SHA256 | [RFC 5869](https://datatracker.ietf.org/doc/html/rfc5869) | [`hkdf`](https://github.com/RustCrypto/KDFs) (RustCrypto) |
491
- | AES-256-GCM | [NIST SP 800-38D](https://csrc.nist.gov/publications/detail/sp/800-38d/final) | [`aes-gcm`](https://github.com/RustCrypto/AEADs) (RustCrypto) |
800
+ | AES-256-GCM | [NIST SP 800-38D](https://csrc.nist.gov/publications/detail/sp/800-38d/final) | [`aes-gcm`](https://github.com/RustCrypto/AEADs) (RustCrypto, AAD support) |
492
801
  | AES-256-CBC | [NIST SP 800-38A](https://csrc.nist.gov/publications/detail/sp/800-38a/final) | [`aes`](https://github.com/RustCrypto/block-ciphers) (RustCrypto) |
493
802
  | HMAC-SHA256 | [RFC 2104](https://datatracker.ietf.org/doc/html/rfc2104) | [`hmac`](https://github.com/RustCrypto/MACs) (RustCrypto) |
494
803
  | SHA-256 | [FIPS 180-4](https://csrc.nist.gov/publications/detail/fips/180/4/final) | [`sha2`](https://github.com/RustCrypto/hashes) (RustCrypto) |
@@ -521,12 +830,17 @@ Benchmarks (Node 22, x86_64):
521
830
  |-----------|------------|-------------|
522
831
  | Curve25519 keygen | ~50,000 ops/sec | **15Γ—** faster than tweetnacl |
523
832
  | X25519 ECDH | ~25,000 ops/sec | **20Γ—** faster |
833
+ | Ed25519 sign | ~25,000 ops/sec | **20Γ—** faster β€” NEW v0.2.0 |
834
+ | Ed25519 verify | ~10,000 ops/sec | **15Γ—** faster β€” NEW v0.2.0 |
835
+ | XEd25519 sign | ~20,000 ops/sec | β€” β€” NEW v0.2.0 |
836
+ | XEd25519 verify | ~10,000 ops/sec | β€” β€” NEW v0.2.0 |
524
837
  | HKDF derive (32 bytes) | ~500,000 ops/sec | **30Γ—** faster |
525
838
  | AES-256-GCM encrypt (1 KB) | ~2 GB/sec | **80Γ—** faster |
839
+ | AES-GCM with AAD | <5% overhead vs no AAD | β€” NEW v0.2.0 |
526
840
  | SHA-256 (1 KB) | ~3 GB/sec | **50Γ—** faster |
527
841
  | HMAC-SHA256 (1 KB) | ~2.5 GB/sec | **40Γ—** faster |
528
842
 
529
- *Run `npm run bench` to benchmark on your machine (coming in v0.2).*
843
+ *Run `npm run bench` to benchmark on your machine (coming in v0.3).*
530
844
 
531
845
  ---
532
846
 
@@ -562,15 +876,20 @@ npm run test:coverage:ui
562
876
 
563
877
  ### What's Tested
564
878
 
565
- - βœ… All RFC test vectors (RFC 5869, 7748, 4231)
879
+ - βœ… All RFC test vectors (RFC 5869, 7748, 8032, 4231)
566
880
  - βœ… All NIST test vectors (AES, SHA-256)
881
+ - βœ… Ed25519 RFC 8032 vector 1 (empty message) β€” NEW v0.2.0
882
+ - βœ… XEd25519 round-trips with deterministic + probabilistic signing β€” NEW v0.2.0
883
+ - βœ… AES-GCM AAD authentication (tampered AAD fails decryption) β€” NEW v0.2.0
567
884
  - βœ… Input validation for every public function
568
885
  - βœ… Error handling for every code path
569
886
  - βœ… Round-trip encryption / decryption
570
- - βœ… Tampering detection (AES-GCM tag failures)
887
+ - βœ… Tampering detection (AES-GCM tag failures, signature failures)
571
888
  - βœ… Both CommonJS and ESM consumption paths
572
889
  - βœ… Default export and named exports
573
890
 
891
+ **Test count: 269+ assertions across Rust (48) + Vitest (172) + CJS (12) + ESM (15) + new v0.2.0 tests (49).**
892
+
574
893
  ---
575
894
 
576
895
  ## πŸ”¨ Building from Source
@@ -611,9 +930,10 @@ npm run examples
611
930
  See [ROADMAP.md](./ROADMAP.md) for detailed plans.
612
931
 
613
932
  **TL;DR:**
614
- - **v0.1** βœ… β€” Cryptographic primitives (current)
615
- - **v0.2** β€” Benchmarks, Ed25519 signatures, X448 support
616
- - **v1.0** β€” Stable API, audit
933
+ - **v0.1** βœ… β€” Cryptographic primitives (Curve25519, HKDF, AES, HMAC, SHA-256)
934
+ - **v0.2** βœ… β€” Ed25519, XEd25519, AES-GCM with AAD (current release)
935
+ - **v0.3** β€” Benchmarks, X448 support, more test vectors
936
+ - **v1.0** β€” Stable API, external audit
617
937
  - **Then:** [@brashkie/signalis](https://github.com/Brashkie/signalis) (X3DH + Double Ratchet)
618
938
  - **Then:** [@brashkie/waproto](https://github.com/Brashkie/waproto) (WhatsApp Protocol)
619
939
  - **Then:** HepeinBaileys 2.0 (full WhatsApp client from scratch)
@@ -638,9 +958,10 @@ Please also read our [Code of Conduct](./CODE_OF_CONDUCT.md).
638
958
  Built on the shoulders of giants:
639
959
 
640
960
  - **[curve25519-dalek](https://github.com/dalek-cryptography/curve25519-dalek)** β€” Curve25519 in pure Rust
961
+ - **[ed25519-dalek](https://github.com/dalek-cryptography/ed25519-dalek)** β€” Ed25519 in pure Rust (added in v0.2.0)
641
962
  - **[RustCrypto](https://github.com/RustCrypto)** β€” `aes`, `hkdf`, `hmac`, `sha2`
642
963
  - **[napi-rs](https://napi.rs)** β€” Rust ↔ Node bindings
643
- - **Signal Foundation** β€” [Protocol specifications](https://signal.org/docs/)
964
+ - **Signal Foundation** β€” [Protocol specifications](https://signal.org/docs/) (including [XEd25519](https://signal.org/docs/specifications/xeddsa/))
644
965
  - **[tsup](https://tsup.egoist.dev/)** β€” Dual ESM/CJS bundler
645
966
  - **[Vitest](https://vitest.dev/)** β€” Modern test runner
646
967
 
@@ -662,4 +983,4 @@ See [LICENSE](./LICENSE) and [NOTICE](./NOTICE) for full details.
662
983
 
663
984
  [GitHub](https://github.com/Brashkie) Β· [npm](https://www.npmjs.com/~brashkie)
664
985
 
665
- </div>
986
+ </div>