@brashkie/signalis-core 0.2.0 → 0.3.1
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/CHANGELOG.md +71 -0
- package/README.es.md +125 -9
- package/README.md +126 -10
- package/dist/index.cjs +106 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +88 -1
- package/dist/index.d.ts +88 -1
- package/dist/index.mjs +100 -3
- package/dist/index.mjs.map +1 -1
- package/index.d.ts +19 -0
- package/index.js +7 -1
- package/package.json +14 -10
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,77 @@ All notable changes to `@brashkie/signalis-core` are documented here.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.3.0] — 2026-06-17
|
|
9
|
+
|
|
10
|
+
### ✨ Added — Multi-Platform Expansion + New Primitives
|
|
11
|
+
|
|
12
|
+
**Focus: Android support + AEAD alternative for ARM-heavy deployments.**
|
|
13
|
+
|
|
14
|
+
#### 🆕 New crates
|
|
15
|
+
- **`sc-chacha20poly1305`** — RFC 8439 ChaCha20-Poly1305 AEAD
|
|
16
|
+
- Encrypt/decrypt with optional AAD
|
|
17
|
+
- Same API shape as `sc-aes`'s GCM helpers
|
|
18
|
+
- 2-3× faster than AES-GCM on ARM without AES-NI hardware
|
|
19
|
+
- Full RFC 8439 test vector compliance
|
|
20
|
+
- **`sc-utils`** — public utility helpers
|
|
21
|
+
- `secure_random(buf)` / `random_bytes(size)` — OS-backed CSPRNG
|
|
22
|
+
- `constant_time_eq(a, b)` — timing-safe comparison
|
|
23
|
+
- `secure_zeroize(buf)` — guaranteed-not-elided buffer wipe
|
|
24
|
+
- 16 MiB cap on single random allocation (DoS guard)
|
|
25
|
+
|
|
26
|
+
#### 🆕 New build targets
|
|
27
|
+
- ✅ `aarch64-linux-android` — Android arm64-v8a (React Native, Termux, modern phones)
|
|
28
|
+
- ✅ `armv7-linux-androideabi` — Android armv7 (older devices, IoT)
|
|
29
|
+
|
|
30
|
+
#### 🆕 Public JS API
|
|
31
|
+
- `ChaCha20Poly1305` namespace mirroring `AES_GCM`:
|
|
32
|
+
- `.encrypt(key, nonce, plaintext)`
|
|
33
|
+
- `.decrypt(key, nonce, ciphertext)`
|
|
34
|
+
- `.encryptWithAad(key, nonce, plaintext, aad)`
|
|
35
|
+
- `.decryptWithAad(key, nonce, ciphertext, aad)`
|
|
36
|
+
- Constants: `KEY_SIZE`, `NONCE_SIZE`, `TAG_SIZE`
|
|
37
|
+
- `constantTimeEq(a, b)` — timing-safe Buffer comparison
|
|
38
|
+
- `nativeSecureRandom(size)` — OS-backed CSPRNG via the native side
|
|
39
|
+
|
|
40
|
+
#### 🆕 CI
|
|
41
|
+
- `cargo-audit` pre-merge gate (fails PRs introducing vulnerable transitive deps)
|
|
42
|
+
- Build matrix now includes both Android targets
|
|
43
|
+
|
|
44
|
+
### 🔄 Changed
|
|
45
|
+
- Workspace `Cargo.toml`: added `chacha20poly1305 = "0.10"` to shared deps
|
|
46
|
+
- `sc-node` re-exports new primitives + helpers from the new crates
|
|
47
|
+
- `package.json` lists `android-arm64` + `android-arm-eabi` as optional sub-packages
|
|
48
|
+
|
|
49
|
+
### ✅ Compatibility
|
|
50
|
+
**100% backwards compatible with v0.2.0.** All existing APIs (`Curve25519`,
|
|
51
|
+
`Ed25519`, `XEd25519`, `HKDF`, `AES_GCM`, `AES_CBC`, `HMAC`, `SHA256`) are
|
|
52
|
+
unchanged. `signalis@0.6.0` (the TypeScript Signal Protocol wrapper) continues
|
|
53
|
+
to work without modifications.
|
|
54
|
+
|
|
55
|
+
### 📦 New install footprint (Android)
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
npm i @brashkie/signalis-core
|
|
59
|
+
# On Android (Termux / React Native), npm/yarn will automatically pull:
|
|
60
|
+
# @brashkie/signalis-core-android-arm64 (or)
|
|
61
|
+
# @brashkie/signalis-core-android-arm-eabi
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### 🔒 Security
|
|
65
|
+
- All new code follows the same audit-friendly patterns as v0.2.0
|
|
66
|
+
- ChaCha20-Poly1305 backed by RustCrypto's `chacha20poly1305@0.10`
|
|
67
|
+
- `secure_random` panics (not silently degrades) if OS RNG is unavailable
|
|
68
|
+
- `constant_time_eq` backed by `subtle@2.5`
|
|
69
|
+
- `secure_zeroize` backed by `zeroize@1.7`
|
|
70
|
+
|
|
71
|
+
### 📋 What's next (v0.4.0)
|
|
72
|
+
- iOS arm64 target
|
|
73
|
+
- WASM target (browsers, via `wasm-bindgen`)
|
|
74
|
+
- PBKDF2 + Argon2id (for the upcoming `signalis-vault` package)
|
|
75
|
+
- Benchmark suite (criterion)
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
8
79
|
## [0.2.0] — 2026-05-22
|
|
9
80
|
|
|
10
81
|
### ✨ Added
|
package/README.es.md
CHANGED
|
@@ -32,25 +32,94 @@ Construida con **Rust** para seguridad y velocidad, expuesta a Node.js mediante
|
|
|
32
32
|
|
|
33
33
|
---
|
|
34
34
|
|
|
35
|
-
## 🎉 Novedades en v0.
|
|
35
|
+
## 🎉 Novedades en v0.3.0
|
|
36
36
|
|
|
37
|
-
**v0.
|
|
37
|
+
**v0.3.0 trae soporte Android + ChaCha20-Poly1305 — totalmente retrocompatible con v0.2.0.**
|
|
38
38
|
|
|
39
39
|
| Nuevo | Descripción |
|
|
40
40
|
|-------|-------------|
|
|
41
|
-
| 🆕 **
|
|
42
|
-
| 🆕 **
|
|
43
|
-
| 🆕 **
|
|
44
|
-
| 🆕
|
|
45
|
-
| 🆕
|
|
41
|
+
| 🆕 **Android arm64-v8a** | Binario nativo para celulares Android modernos (React Native, Termux) |
|
|
42
|
+
| 🆕 **Android armv7** | Binario nativo para dispositivos Android más viejos (Android 4.4+) |
|
|
43
|
+
| 🆕 **ChaCha20-Poly1305** | AEAD según RFC 8439 — 2-3× más rápido que AES-GCM en ARM sin AES-NI |
|
|
44
|
+
| 🆕 **`constantTimeEq()`** | Comparación de Buffer en tiempo constante (para verificar MAC/firmas) |
|
|
45
|
+
| 🆕 **`nativeSecureRandom()`** | CSPRNG basado en OS vía Rust (alternativa a `secureRandom` JS-side) |
|
|
46
|
+
| 🆕 **Crate `sc-utils`** | Helpers utilitarios públicos (random, constant_time_eq, secure_zeroize) |
|
|
47
|
+
| 🆕 **CI: gate cargo-audit** | Dependencias transitivas vulnerables ahora bloquean PRs automáticamente |
|
|
48
|
+
|
|
49
|
+
### Ejemplo rápido: 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('¡Hola!'));
|
|
58
|
+
const pt = ChaCha20Poly1305.decrypt(key, nonce, ct); // → "¡Hola!"
|
|
59
|
+
|
|
60
|
+
// Con Datos Autenticados Adicionales
|
|
61
|
+
const aad = Buffer.from('metadata del header');
|
|
62
|
+
const ct2 = ChaCha20Poly1305.encryptWithAad(key, nonce, plaintext, aad);
|
|
63
|
+
const pt2 = ChaCha20Poly1305.decryptWithAad(key, nonce, ct2, aad);
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### ¿Por qué usar ChaCha20-Poly1305 en lugar de AES-GCM?
|
|
67
|
+
|
|
68
|
+
Usá **AES-GCM** cuando corras en:
|
|
69
|
+
- Servidores x86_64 (Intel/AMD con hardware AES-NI)
|
|
70
|
+
- Desktops modernos
|
|
71
|
+
- CPUs ARM nuevos con Extensiones Cripto ARMv8
|
|
72
|
+
|
|
73
|
+
Usá **ChaCha20-Poly1305** cuando corras en:
|
|
74
|
+
- Celulares Android (la mayoría no tiene AES-NI)
|
|
75
|
+
- IoT / dispositivos embebidos
|
|
76
|
+
- Cualquier cosa sin hardware AES acelerado
|
|
77
|
+
|
|
78
|
+
**Ambos tienen garantías de seguridad idénticas.** La diferencia es puramente de rendimiento, dependiendo de si tu target tiene hardware AES.
|
|
46
79
|
|
|
47
80
|
Ver [MIGRATION.md](./MIGRATION.md) para detalles de upgrade (es un drop-in replacement).
|
|
48
81
|
|
|
49
82
|
---
|
|
50
83
|
|
|
84
|
+
## 🌍 Plataformas Soportadas
|
|
85
|
+
|
|
86
|
+
`@brashkie/signalis-core` envía binarios nativos pre-compilados para **9 plataformas** vía `optionalDependencies` de npm. El binario correcto se descarga automáticamente según tu sistema operativo + arquitectura.
|
|
87
|
+
|
|
88
|
+
| SO | Arquitectura | Sub-paquete | Estado |
|
|
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
|
+
Próximas plataformas en v0.4.0: **iOS arm64**, **WASM (navegadores)**, **FreeBSD x64**.
|
|
101
|
+
|
|
102
|
+
### Instalación en Android
|
|
103
|
+
|
|
104
|
+
El mismo `npm install` funciona en cualquier entorno Node.js corriendo en Android, incluyendo:
|
|
105
|
+
- **Termux** en celulares Android (`pkg install nodejs`)
|
|
106
|
+
- **React Native** con target Android
|
|
107
|
+
- Apps con **NodeJS-Mobile**
|
|
108
|
+
- Builds personalizados de Node embebido para IoT
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
# En Android (Termux por ejemplo):
|
|
112
|
+
pkg install nodejs
|
|
113
|
+
npm install @brashkie/signalis-core
|
|
114
|
+
# → npm descarga automáticamente signalis-core-android-arm64
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
51
119
|
## 📋 Tabla de Contenido
|
|
52
120
|
|
|
53
121
|
- [Características](#-características)
|
|
122
|
+
- [Plataformas Soportadas](#-plataformas-soportadas)
|
|
54
123
|
- [¿Por qué Signalis Core?](#-por-qué-signalis-core)
|
|
55
124
|
- [Instalación](#-instalación)
|
|
56
125
|
- [Inicio Rápido](#-inicio-rápido)
|
|
@@ -79,7 +148,7 @@ Ver [MIGRATION.md](./MIGRATION.md) para detalles de upgrade (es un drop-in repla
|
|
|
79
148
|
| 📦 **Paquete Dual** | Funciona en proyectos CommonJS, ESM y TypeScript |
|
|
80
149
|
| 🎯 **Tipado Estricto** | Definiciones TypeScript completas con tipos branded y clases de error |
|
|
81
150
|
| ✅ **Vectores de Test** | Validado contra RFC 5869, RFC 7748, RFC 8032, RFC 4231 y vectores NIST |
|
|
82
|
-
| 🌍 **Multi-Plataforma** | Binarios pre-compilados para Windows, macOS, Linux (x64, ARM) |
|
|
151
|
+
| 🌍 **Multi-Plataforma** | Binarios pre-compilados para Windows, macOS, Linux (x64, ARM) + **Android arm64/armv7** |
|
|
83
152
|
| 🔒 **Tiempo Constante** | Comparaciones resistentes a side-channel via crate `subtle` |
|
|
84
153
|
| 🧹 **Auto-Borrado** | Secretos se borran de memoria automáticamente |
|
|
85
154
|
| 📊 **Cobertura 99%+** | Suite de tests con 269+ aserciones |
|
|
@@ -392,6 +461,43 @@ if (HMAC.verifySha256(macKey, concat([iv, ct]), tag)) {
|
|
|
392
461
|
}
|
|
393
462
|
```
|
|
394
463
|
|
|
464
|
+
### ChaCha20-Poly1305 🆕
|
|
465
|
+
|
|
466
|
+
Cifrado autenticado según RFC 8439. Alternativa a AES-GCM, más rápido en ARM sin AES-NI.
|
|
467
|
+
|
|
468
|
+
```typescript
|
|
469
|
+
import { ChaCha20Poly1305, secureRandom } from '@brashkie/signalis-core';
|
|
470
|
+
|
|
471
|
+
const clave = secureRandom(32); // ChaCha20Poly1305.KEY_SIZE
|
|
472
|
+
const nonce = secureRandom(12); // ChaCha20Poly1305.NONCE_SIZE
|
|
473
|
+
|
|
474
|
+
// Cifrado/descifrado básico (sin AAD)
|
|
475
|
+
const ct = ChaCha20Poly1305.encrypt(clave, nonce, textoplano);
|
|
476
|
+
const pt = ChaCha20Poly1305.decrypt(clave, nonce, ct);
|
|
477
|
+
|
|
478
|
+
// Con Datos Autenticados Adicionales
|
|
479
|
+
const aad = Buffer.from('header público');
|
|
480
|
+
const ct2 = ChaCha20Poly1305.encryptWithAad(clave, nonce, textoplano, aad);
|
|
481
|
+
const pt2 = ChaCha20Poly1305.decryptWithAad(clave, nonce, ct2, aad);
|
|
482
|
+
|
|
483
|
+
// Constantes
|
|
484
|
+
ChaCha20Poly1305.KEY_SIZE; // 32
|
|
485
|
+
ChaCha20Poly1305.NONCE_SIZE; // 12
|
|
486
|
+
ChaCha20Poly1305.TAG_SIZE; // 16 (anexado al ciphertext)
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
**Rendimiento vs AES-GCM:**
|
|
490
|
+
|
|
491
|
+
| Target | Ganador | Por qué |
|
|
492
|
+
|--------|---------|---------|
|
|
493
|
+
| Server x86_64 (AES-NI) | AES-GCM | Aceleración por hardware |
|
|
494
|
+
| Desktop moderno (AES-NI) | AES-GCM | Aceleración por hardware |
|
|
495
|
+
| Android arm64 (sin ARMv8 crypto) | **ChaCha20-Poly1305** | CPU puro, ChaCha es más rápido |
|
|
496
|
+
| IoT / embebido | **ChaCha20-Poly1305** | No requiere hardware AES |
|
|
497
|
+
| Apple Silicon (serie M) | Cualquiera (parejos) | Ambos bien optimizados |
|
|
498
|
+
|
|
499
|
+
**Seguridad:** ChaCha20-Poly1305 y AES-GCM tienen propiedades de seguridad equivalentes. La decisión es puramente sobre rendimiento en tu target.
|
|
500
|
+
|
|
395
501
|
### HMAC-SHA256
|
|
396
502
|
|
|
397
503
|
Autenticación de mensajes.
|
|
@@ -433,7 +539,9 @@ import {
|
|
|
433
539
|
fromBase64Url,
|
|
434
540
|
|
|
435
541
|
// Seguridad
|
|
436
|
-
constantTimeEqual,
|
|
542
|
+
constantTimeEqual, // (a, b) → boolean (timing-safe, JS-side)
|
|
543
|
+
constantTimeEq, // 🆕 v0.3.0 — igual pero vía Rust side
|
|
544
|
+
nativeSecureRandom, // 🆕 v0.3.0 — CSPRNG del OS vía Rust (vs node:crypto)
|
|
437
545
|
|
|
438
546
|
// Operaciones Buffer
|
|
439
547
|
concat, // (buffers[]) → Buffer
|
|
@@ -442,6 +550,14 @@ import {
|
|
|
442
550
|
} from '@brashkie/signalis-core';
|
|
443
551
|
```
|
|
444
552
|
|
|
553
|
+
#### ¿Cuándo usar `constantTimeEq` vs `constantTimeEqual`?
|
|
554
|
+
|
|
555
|
+
Ambos comparan en tiempo constante. `constantTimeEqual` usa la semántica de `Buffer.compare` de JavaScript (implementación JS-side). `constantTimeEq` (v0.3.0) pasa por la crate auditada `subtle::ConstantTimeEq` de Rust. Las dos son seguras — usá `constantTimeEq` si querés asegurarte de que tu camino timing-safe sea el mismo que usa el resto de la librería internamente.
|
|
556
|
+
|
|
557
|
+
#### ¿Cuándo usar `nativeSecureRandom` vs `secureRandom`?
|
|
558
|
+
|
|
559
|
+
`secureRandom(n)` usa `crypto.randomBytes()` de Node. `nativeSecureRandom(n)` (v0.3.0) pasa por `OsRng` de Rust (que llama directamente a `getrandom`/`BCryptGenRandom`/`SecRandomCopyBytes`). Ambos son criptográficamente seguros. Usá `nativeSecureRandom` si querés una sola superficie de auditoría para entropía en todo tu stack.
|
|
560
|
+
|
|
445
561
|
### Errores
|
|
446
562
|
|
|
447
563
|
Todos los errores extienden `SignalisError`:
|
package/README.md
CHANGED
|
@@ -32,27 +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.
|
|
35
|
+
## 🎉 What's New in v0.3.0
|
|
36
36
|
|
|
37
|
-
**v0.
|
|
37
|
+
**v0.3.0 ships Android support + ChaCha20-Poly1305 — fully backwards compatible with v0.2.0.**
|
|
38
38
|
|
|
39
39
|
| New | Description |
|
|
40
40
|
|-----|-------------|
|
|
41
|
-
| 🆕 **
|
|
42
|
-
| 🆕 **
|
|
43
|
-
| 🆕 **
|
|
44
|
-
| 🆕
|
|
45
|
-
| 🆕
|
|
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.
|
|
46
79
|
|
|
47
80
|
See [MIGRATION.md](./MIGRATION.md) for upgrade details (it's a drop-in replacement).
|
|
48
81
|
|
|
49
82
|
---
|
|
50
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
|
+
|
|
51
119
|
## 📋 Table of Contents
|
|
52
120
|
|
|
53
121
|
- [🔐 Signalis Core](#-signalis-core)
|
|
54
122
|
- [✨ What is Signalis Core?](#-what-is-signalis-core)
|
|
55
|
-
- [🎉 What's New in v0.
|
|
123
|
+
- [🎉 What's New in v0.3.0](#-whats-new-in-v030)
|
|
124
|
+
- [🌍 Supported Platforms](#-supported-platforms)
|
|
56
125
|
- [📋 Table of Contents](#-table-of-contents)
|
|
57
126
|
- [🚀 Features](#-features)
|
|
58
127
|
- [🤔 Why Signalis Core?](#-why-signalis-core)
|
|
@@ -109,7 +178,7 @@ See [MIGRATION.md](./MIGRATION.md) for upgrade details (it's a drop-in replaceme
|
|
|
109
178
|
| 📦 **Dual Package** | Works in CommonJS, ESM, and TypeScript projects |
|
|
110
179
|
| 🎯 **Type-Safe** | Full TypeScript definitions with branded types and rich error classes |
|
|
111
180
|
| ✅ **Test Vectors** | Validated against RFC 5869, RFC 7748, RFC 8032, RFC 4231, and NIST vectors |
|
|
112
|
-
| 🌍 **Cross-Platform** | Prebuilt binaries for Windows, macOS, Linux (x64, ARM) |
|
|
181
|
+
| 🌍 **Cross-Platform** | Prebuilt binaries for Windows, macOS, Linux (x64, ARM) + **Android arm64/armv7** |
|
|
113
182
|
| 🔒 **Constant-Time** | Side-channel resistant comparisons via `subtle` crate |
|
|
114
183
|
| 🧹 **Auto-Zeroization** | Secrets are wiped from memory automatically |
|
|
115
184
|
| 📊 **99%+ Coverage** | Comprehensive test suite with 269+ assertions |
|
|
@@ -426,6 +495,43 @@ if (HMAC.verifySha256(macKey, concat([iv, ct]), tag)) {
|
|
|
426
495
|
}
|
|
427
496
|
```
|
|
428
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
|
+
|
|
429
535
|
### HMAC-SHA256
|
|
430
536
|
|
|
431
537
|
Message authentication.
|
|
@@ -467,7 +573,9 @@ import {
|
|
|
467
573
|
fromBase64Url,
|
|
468
574
|
|
|
469
575
|
// Security
|
|
470
|
-
constantTimeEqual,
|
|
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)
|
|
471
579
|
|
|
472
580
|
// Buffer ops
|
|
473
581
|
concat, // (buffers[]) → Buffer
|
|
@@ -476,6 +584,14 @@ import {
|
|
|
476
584
|
} from '@brashkie/signalis-core';
|
|
477
585
|
```
|
|
478
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
|
+
|
|
479
595
|
### Errors
|
|
480
596
|
|
|
481
597
|
All errors extend `SignalisError`:
|
package/dist/index.cjs
CHANGED
|
@@ -40,9 +40,13 @@ __export(index_exports, {
|
|
|
40
40
|
AES_GCM_MAX_PLAINTEXT_SIZE: () => AES_GCM_MAX_PLAINTEXT_SIZE,
|
|
41
41
|
AES_GCM_RECOMMENDED_MESSAGES_PER_KEY: () => AES_GCM_RECOMMENDED_MESSAGES_PER_KEY,
|
|
42
42
|
AuthenticationError: () => AuthenticationError,
|
|
43
|
+
CHACHA20_POLY1305_KEY_SIZE: () => CHACHA20_POLY1305_KEY_SIZE,
|
|
44
|
+
CHACHA20_POLY1305_NONCE_SIZE: () => CHACHA20_POLY1305_NONCE_SIZE,
|
|
45
|
+
CHACHA20_POLY1305_TAG_SIZE: () => CHACHA20_POLY1305_TAG_SIZE,
|
|
43
46
|
CURVE25519_PRIVATE_KEY_SIZE: () => CURVE25519_PRIVATE_KEY_SIZE,
|
|
44
47
|
CURVE25519_PUBLIC_KEY_SIZE: () => CURVE25519_PUBLIC_KEY_SIZE,
|
|
45
48
|
CURVE25519_SHARED_SECRET_SIZE: () => CURVE25519_SHARED_SECRET_SIZE,
|
|
49
|
+
ChaCha20Poly1305: () => ChaCha20Poly1305,
|
|
46
50
|
CryptoError: () => CryptoError,
|
|
47
51
|
Curve25519: () => Curve25519,
|
|
48
52
|
ED25519_PRIVATE_KEY_SIZE: () => ED25519_PRIVATE_KEY_SIZE,
|
|
@@ -81,16 +85,18 @@ __export(index_exports, {
|
|
|
81
85
|
bufferToString: () => bufferToString,
|
|
82
86
|
buffersSameLength: () => buffersSameLength,
|
|
83
87
|
concat: () => concat,
|
|
88
|
+
constantTimeEq: () => constantTimeEq2,
|
|
84
89
|
constantTimeEqual: () => constantTimeEqual,
|
|
85
90
|
default: () => index_default,
|
|
86
91
|
fromBase64: () => fromBase64,
|
|
87
92
|
fromBase64Url: () => fromBase64Url,
|
|
88
93
|
fromHex: () => fromHex,
|
|
94
|
+
nativeSecureRandom: () => nativeSecureRandom,
|
|
89
95
|
nativeVersion: () => nativeVersion,
|
|
90
96
|
randomIv: () => randomIv,
|
|
91
97
|
randomKey: () => randomKey,
|
|
92
98
|
randomNonce: () => randomNonce,
|
|
93
|
-
secureRandom: () =>
|
|
99
|
+
secureRandom: () => secureRandom2,
|
|
94
100
|
stringToBuffer: () => stringToBuffer,
|
|
95
101
|
toBase64: () => toBase64,
|
|
96
102
|
toBase64Url: () => toBase64Url,
|
|
@@ -736,6 +742,97 @@ var XEd25519 = Object.freeze({
|
|
|
736
742
|
RANDOM_SIZE: XED25519_RANDOM_SIZE
|
|
737
743
|
});
|
|
738
744
|
var nativeVersion = native.version();
|
|
745
|
+
var CHACHA20_POLY1305_KEY_SIZE = 32;
|
|
746
|
+
var CHACHA20_POLY1305_NONCE_SIZE = 12;
|
|
747
|
+
var CHACHA20_POLY1305_TAG_SIZE = 16;
|
|
748
|
+
var ChaCha20Poly1305 = Object.freeze({
|
|
749
|
+
/**
|
|
750
|
+
* Encrypt + authenticate `plaintext`.
|
|
751
|
+
*
|
|
752
|
+
* @param key 32-byte key
|
|
753
|
+
* @param nonce 12-byte nonce — MUST be unique per (key, plaintext)
|
|
754
|
+
* @param plaintext data to encrypt
|
|
755
|
+
* @returns ciphertext || tag (16 bytes appended)
|
|
756
|
+
*/
|
|
757
|
+
encrypt(key, nonce, plaintext) {
|
|
758
|
+
if (!Buffer.isBuffer(key) || key.length !== CHACHA20_POLY1305_KEY_SIZE) {
|
|
759
|
+
throw new RangeError(`key must be ${CHACHA20_POLY1305_KEY_SIZE} bytes`);
|
|
760
|
+
}
|
|
761
|
+
if (!Buffer.isBuffer(nonce) || nonce.length !== CHACHA20_POLY1305_NONCE_SIZE) {
|
|
762
|
+
throw new RangeError(`nonce must be ${CHACHA20_POLY1305_NONCE_SIZE} bytes`);
|
|
763
|
+
}
|
|
764
|
+
if (!Buffer.isBuffer(plaintext)) {
|
|
765
|
+
throw new TypeError("plaintext must be a Buffer");
|
|
766
|
+
}
|
|
767
|
+
return native.chacha20Poly1305Encrypt(key, nonce, plaintext);
|
|
768
|
+
},
|
|
769
|
+
/**
|
|
770
|
+
* Verify-then-decrypt. Returns plaintext on success, throws on auth failure.
|
|
771
|
+
*/
|
|
772
|
+
decrypt(key, nonce, ciphertext) {
|
|
773
|
+
if (!Buffer.isBuffer(key) || key.length !== CHACHA20_POLY1305_KEY_SIZE) {
|
|
774
|
+
throw new RangeError(`key must be ${CHACHA20_POLY1305_KEY_SIZE} bytes`);
|
|
775
|
+
}
|
|
776
|
+
if (!Buffer.isBuffer(nonce) || nonce.length !== CHACHA20_POLY1305_NONCE_SIZE) {
|
|
777
|
+
throw new RangeError(`nonce must be ${CHACHA20_POLY1305_NONCE_SIZE} bytes`);
|
|
778
|
+
}
|
|
779
|
+
if (!Buffer.isBuffer(ciphertext)) {
|
|
780
|
+
throw new TypeError("ciphertext must be a Buffer");
|
|
781
|
+
}
|
|
782
|
+
return native.chacha20Poly1305Decrypt(key, nonce, ciphertext);
|
|
783
|
+
},
|
|
784
|
+
/**
|
|
785
|
+
* Encrypt + authenticate with Additional Authenticated Data.
|
|
786
|
+
*
|
|
787
|
+
* AAD is NOT encrypted but IS authenticated. Use for plaintext metadata
|
|
788
|
+
* (e.g., message headers) that must not be tampered with.
|
|
789
|
+
*/
|
|
790
|
+
encryptWithAad(key, nonce, plaintext, aad) {
|
|
791
|
+
if (!Buffer.isBuffer(key) || key.length !== CHACHA20_POLY1305_KEY_SIZE) {
|
|
792
|
+
throw new RangeError(`key must be ${CHACHA20_POLY1305_KEY_SIZE} bytes`);
|
|
793
|
+
}
|
|
794
|
+
if (!Buffer.isBuffer(nonce) || nonce.length !== CHACHA20_POLY1305_NONCE_SIZE) {
|
|
795
|
+
throw new RangeError(`nonce must be ${CHACHA20_POLY1305_NONCE_SIZE} bytes`);
|
|
796
|
+
}
|
|
797
|
+
if (!Buffer.isBuffer(plaintext) || !Buffer.isBuffer(aad)) {
|
|
798
|
+
throw new TypeError("plaintext and aad must be Buffers");
|
|
799
|
+
}
|
|
800
|
+
return native.chacha20Poly1305EncryptWithAad(key, nonce, plaintext, aad);
|
|
801
|
+
},
|
|
802
|
+
/**
|
|
803
|
+
* Verify (key + nonce + ciphertext + AAD) and decrypt.
|
|
804
|
+
*/
|
|
805
|
+
decryptWithAad(key, nonce, ciphertext, aad) {
|
|
806
|
+
if (!Buffer.isBuffer(key) || key.length !== CHACHA20_POLY1305_KEY_SIZE) {
|
|
807
|
+
throw new RangeError(`key must be ${CHACHA20_POLY1305_KEY_SIZE} bytes`);
|
|
808
|
+
}
|
|
809
|
+
if (!Buffer.isBuffer(nonce) || nonce.length !== CHACHA20_POLY1305_NONCE_SIZE) {
|
|
810
|
+
throw new RangeError(`nonce must be ${CHACHA20_POLY1305_NONCE_SIZE} bytes`);
|
|
811
|
+
}
|
|
812
|
+
if (!Buffer.isBuffer(ciphertext) || !Buffer.isBuffer(aad)) {
|
|
813
|
+
throw new TypeError("ciphertext and aad must be Buffers");
|
|
814
|
+
}
|
|
815
|
+
return native.chacha20Poly1305DecryptWithAad(key, nonce, ciphertext, aad);
|
|
816
|
+
},
|
|
817
|
+
/** Key size in bytes (32). */
|
|
818
|
+
KEY_SIZE: CHACHA20_POLY1305_KEY_SIZE,
|
|
819
|
+
/** Nonce size in bytes (12). */
|
|
820
|
+
NONCE_SIZE: CHACHA20_POLY1305_NONCE_SIZE,
|
|
821
|
+
/** Authentication tag size in bytes (16), appended to ciphertext. */
|
|
822
|
+
TAG_SIZE: CHACHA20_POLY1305_TAG_SIZE
|
|
823
|
+
});
|
|
824
|
+
function constantTimeEq2(a, b) {
|
|
825
|
+
if (!Buffer.isBuffer(a) || !Buffer.isBuffer(b)) {
|
|
826
|
+
throw new TypeError("constantTimeEq: both arguments must be Buffers");
|
|
827
|
+
}
|
|
828
|
+
return native.constantTimeEq(a, b);
|
|
829
|
+
}
|
|
830
|
+
function nativeSecureRandom(size) {
|
|
831
|
+
if (!Number.isInteger(size) || size <= 0) {
|
|
832
|
+
throw new RangeError(`size must be a positive integer, got ${size}`);
|
|
833
|
+
}
|
|
834
|
+
return native.secureRandom(size);
|
|
835
|
+
}
|
|
739
836
|
|
|
740
837
|
// src/types.ts
|
|
741
838
|
function asPublicKey(buf) {
|
|
@@ -753,7 +850,7 @@ function asSignature(buf) {
|
|
|
753
850
|
|
|
754
851
|
// src/utils.ts
|
|
755
852
|
var import_node_crypto = require("crypto");
|
|
756
|
-
function
|
|
853
|
+
function secureRandom2(length) {
|
|
757
854
|
if (!Number.isInteger(length) || length < 0) {
|
|
758
855
|
throw new RangeError(`length must be a non-negative integer, got ${length}`);
|
|
759
856
|
}
|
|
@@ -835,7 +932,7 @@ var SignalisCore = Object.freeze({
|
|
|
835
932
|
HMAC,
|
|
836
933
|
SHA256,
|
|
837
934
|
// Random
|
|
838
|
-
secureRandom,
|
|
935
|
+
secureRandom: secureRandom2,
|
|
839
936
|
randomNonce,
|
|
840
937
|
randomIv,
|
|
841
938
|
randomKey,
|
|
@@ -863,9 +960,13 @@ var index_default = SignalisCore;
|
|
|
863
960
|
AES_GCM_MAX_PLAINTEXT_SIZE,
|
|
864
961
|
AES_GCM_RECOMMENDED_MESSAGES_PER_KEY,
|
|
865
962
|
AuthenticationError,
|
|
963
|
+
CHACHA20_POLY1305_KEY_SIZE,
|
|
964
|
+
CHACHA20_POLY1305_NONCE_SIZE,
|
|
965
|
+
CHACHA20_POLY1305_TAG_SIZE,
|
|
866
966
|
CURVE25519_PRIVATE_KEY_SIZE,
|
|
867
967
|
CURVE25519_PUBLIC_KEY_SIZE,
|
|
868
968
|
CURVE25519_SHARED_SECRET_SIZE,
|
|
969
|
+
ChaCha20Poly1305,
|
|
869
970
|
CryptoError,
|
|
870
971
|
Curve25519,
|
|
871
972
|
ED25519_PRIVATE_KEY_SIZE,
|
|
@@ -904,10 +1005,12 @@ var index_default = SignalisCore;
|
|
|
904
1005
|
bufferToString,
|
|
905
1006
|
buffersSameLength,
|
|
906
1007
|
concat,
|
|
1008
|
+
constantTimeEq,
|
|
907
1009
|
constantTimeEqual,
|
|
908
1010
|
fromBase64,
|
|
909
1011
|
fromBase64Url,
|
|
910
1012
|
fromHex,
|
|
1013
|
+
nativeSecureRandom,
|
|
911
1014
|
nativeVersion,
|
|
912
1015
|
randomIv,
|
|
913
1016
|
randomKey,
|