@brashkie/signalis-core 0.3.1 → 0.4.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/CHANGELOG.md +88 -0
- package/README.es.md +110 -4
- package/README.md +111 -4
- package/dist/index.cjs +133 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +139 -3
- package/dist/index.d.ts +139 -3
- package/dist/index.mjs +130 -3
- package/dist/index.mjs.map +1 -1
- package/index.d.ts +35 -0
- package/index.js +12 -1
- package/package.json +14 -11
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,94 @@ 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.4.0] — 2026-07-08
|
|
9
|
+
|
|
10
|
+
### ✨ Added — Encoding helpers + Android x86_64
|
|
11
|
+
|
|
12
|
+
v0.4.0 introduces native encoding helpers (Base64, Hex, UTF-8) and adds
|
|
13
|
+
Android x86_64 to the supported platform list. Fully backwards compatible
|
|
14
|
+
with v0.3.x.
|
|
15
|
+
|
|
16
|
+
#### 🆕 Encoding namespaces (Rust-side implementations)
|
|
17
|
+
|
|
18
|
+
Three new namespaces exported from the top level, backed by the new
|
|
19
|
+
`sc-encoding` crate. All operations are audited RustCrypto ecosystem
|
|
20
|
+
routines with strict validation (no lossy conversions).
|
|
21
|
+
|
|
22
|
+
- **`Base64`**
|
|
23
|
+
- `encode(bytes) → string` — RFC 4648 standard (with `=` padding)
|
|
24
|
+
- `decode(string) → Buffer` — throws on invalid input
|
|
25
|
+
- `encodeUrlSafe(bytes) → string` — `-` and `_` alphabet, no padding
|
|
26
|
+
- `decodeUrlSafe(string) → Buffer`
|
|
27
|
+
|
|
28
|
+
- **`Hex`**
|
|
29
|
+
- `encode(bytes) → string` — lowercase output
|
|
30
|
+
- `encodeUpper(bytes) → string` — uppercase output
|
|
31
|
+
- `decode(string) → Buffer` — case-insensitive
|
|
32
|
+
- `isValid(string) → boolean` — cheap format check
|
|
33
|
+
|
|
34
|
+
- **`Utf8`**
|
|
35
|
+
- `encode(string) → Buffer` — UTF-8 bytes
|
|
36
|
+
- `decode(bytes) → string` — **strict** validation, throws on invalid
|
|
37
|
+
UTF-8 (unlike `Buffer.toString('utf-8')` which silently substitutes U+FFFD)
|
|
38
|
+
- `isValid(bytes) → boolean`
|
|
39
|
+
|
|
40
|
+
Quick example:
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
import { Base64, Hex, Utf8 } from '@brashkie/signalis-core';
|
|
44
|
+
|
|
45
|
+
const bytes = Utf8.encode('Hola 🦀');
|
|
46
|
+
const b64 = Base64.encode(bytes); // 'SG9sYSDwn6aA'
|
|
47
|
+
const hex = Hex.encode(bytes); // '486f6c6120f09fa680'
|
|
48
|
+
const back = Utf8.decode(Base64.decode(b64)); // 'Hola 🦀'
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
#### 🆕 New crate: `sc-encoding`
|
|
52
|
+
|
|
53
|
+
A dedicated Rust crate for encoding routines, separated from `sc-utils`
|
|
54
|
+
to keep encoding logic isolated from cryptographic utilities. This makes
|
|
55
|
+
the codebase easier to audit and lets adopters pull in just what they need.
|
|
56
|
+
|
|
57
|
+
Dependencies (all RustCrypto ecosystem):
|
|
58
|
+
- `base64` v0.22 — RFC 4648 encoder/decoder
|
|
59
|
+
- `hex` v0.4 — Base16 encoder/decoder
|
|
60
|
+
- Rust standard library for UTF-8 validation
|
|
61
|
+
|
|
62
|
+
#### 🆕 Android x86_64 support
|
|
63
|
+
|
|
64
|
+
New sub-package `@brashkie/signalis-core-android-x64` published to npm.
|
|
65
|
+
Primary use cases:
|
|
66
|
+
- **Android Emulator** — runs x86_64, so devs testing apps in Android
|
|
67
|
+
Studio's emulator now get native binaries automatically
|
|
68
|
+
- **Termux on x86 tablets / Chromebooks** — install via
|
|
69
|
+
`pkg install nodejs && npm install @brashkie/signalis-core`
|
|
70
|
+
|
|
71
|
+
Total platforms supported: **10** (was 9 in v0.3.1).
|
|
72
|
+
|
|
73
|
+
### 🔄 Changed
|
|
74
|
+
- `VERSION` bumped to `'0.4.0'`
|
|
75
|
+
- `Cargo.toml` workspace: version 0.3.x → 0.4.0
|
|
76
|
+
- `sc-node`: 10 new `#[napi]` exports for the encoding functions
|
|
77
|
+
- `optionalDependencies` in `package.json`: adds `android-x64` (10 sub-packages total)
|
|
78
|
+
- `napi.triples.additional`: adds `x86_64-linux-android`
|
|
79
|
+
- `.github/workflows/release.yml`: adds `x86_64-linux-android` build job
|
|
80
|
+
- `scripts/create-npm-dirs.js`: adds `android-x64` to `PLATFORMS` array
|
|
81
|
+
|
|
82
|
+
### ✅ Compatibility
|
|
83
|
+
**100% backwards compatible with v0.3.x.** No API changes. No breaking
|
|
84
|
+
behavior. Existing code continues to work; the encoding namespaces are
|
|
85
|
+
purely additive.
|
|
86
|
+
|
|
87
|
+
### 📋 What's next (v0.5.0 candidates)
|
|
88
|
+
- Buffer / ByteArray utility helpers (`sc-encoding` extension)
|
|
89
|
+
- XChaCha20-Poly1305 (extended-nonce AEAD)
|
|
90
|
+
- Argon2id password hashing
|
|
91
|
+
- PBKDF2-SHA256
|
|
92
|
+
- HKDF-SHA512
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
8
96
|
## [0.3.0] — 2026-06-17
|
|
9
97
|
|
|
10
98
|
### ✨ Added — Multi-Platform Expansion + New Primitives
|
package/README.es.md
CHANGED
|
@@ -32,6 +32,71 @@ Construida con **Rust** para seguridad y velocidad, expuesta a Node.js mediante
|
|
|
32
32
|
|
|
33
33
|
---
|
|
34
34
|
|
|
35
|
+
## 🎉 Novedades en v0.4.0
|
|
36
|
+
|
|
37
|
+
**v0.4.0 introduce helpers nativos de encoding (Base64, Hex, UTF-8) + soporte para Android x86_64 — totalmente retrocompatible con v0.3.x.**
|
|
38
|
+
|
|
39
|
+
| Nuevo | Descripción |
|
|
40
|
+
|-------|-------------|
|
|
41
|
+
| 🆕 **Namespace `Base64`** | RFC 4648 estándar + variante URL-safe (sin padding), implementación Rust |
|
|
42
|
+
| 🆕 **Namespace `Hex`** | Encode minúsculas/mayúsculas, decode case-insensitive, `isValid()` chequeo rápido |
|
|
43
|
+
| 🆕 **Namespace `Utf8`** | Validación UTF-8 **estricta** — tira error en bytes malformados (a diferencia de `Buffer.toString('utf-8')` que sustituye silenciosamente por U+FFFD) |
|
|
44
|
+
| 🆕 **Android x86_64** | Binario nativo para el Android Emulator + Chromebooks + tablets x86 |
|
|
45
|
+
| 🆕 **Crate `sc-encoding`** | Crate Rust dedicado — separa la lógica de encoding de la criptografía para facilitar auditoría |
|
|
46
|
+
|
|
47
|
+
### Ejemplo rápido: Base64 / Hex / Utf8
|
|
48
|
+
|
|
49
|
+
```ts
|
|
50
|
+
import { Base64, Hex, Utf8 } from '@brashkie/signalis-core';
|
|
51
|
+
|
|
52
|
+
// UTF-8 con validación estricta
|
|
53
|
+
const bytes = Utf8.encode('Hola 🦀'); // Buffer <48 6f 6c 61 20 f0 9f a6 80>
|
|
54
|
+
const roundTrip = Utf8.decode(bytes); // "Hola 🦀"
|
|
55
|
+
|
|
56
|
+
// Base64 estándar (con padding)
|
|
57
|
+
const b64 = Base64.encode(bytes); // "SG9sYSDwn6aA"
|
|
58
|
+
const decoded = Base64.decode(b64); // coincide con el original
|
|
59
|
+
|
|
60
|
+
// Base64 URL-safe (sin padding, sin + ni /)
|
|
61
|
+
const urlSafe = Base64.encodeUrlSafe(bytes); // seguro en URLs, filenames, JWTs
|
|
62
|
+
|
|
63
|
+
// Hex
|
|
64
|
+
const hex = Hex.encode(bytes); // "486f6c6120f09fa680"
|
|
65
|
+
const hexUpper = Hex.encodeUpper(bytes); // "486F6C6120F09FA680"
|
|
66
|
+
const back = Hex.decode('DEADBEEF'); // case-insensitive
|
|
67
|
+
Hex.isValid('deadbeef'); // true
|
|
68
|
+
Hex.isValid('nope!'); // false
|
|
69
|
+
|
|
70
|
+
// Validación UTF-8 (barata, sin allocation)
|
|
71
|
+
Utf8.isValid(Buffer.from([0xff])); // false (0xff nunca es UTF-8 válido)
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### ¿Por qué importa la validación UTF-8 estricta?
|
|
75
|
+
|
|
76
|
+
El `Buffer.toString('utf-8')` nativo de Node **sustituye silenciosamente por U+FFFD** (el carácter de reemplazo) cuando encuentra bytes inválidos. Para código de aplicación normal está bien. Pero cuando estás procesando mensajes firmados, verificando MACs, o manejando input de un atacante potencial, **querés saber** si los bytes están malformados en lugar de continuar con datos silenciosamente corruptos.
|
|
77
|
+
|
|
78
|
+
```ts
|
|
79
|
+
const badBytes = Buffer.from([0xff, 0xfe]);
|
|
80
|
+
|
|
81
|
+
// Node built-in: no error, produce "\uFFFD\uFFFD"
|
|
82
|
+
badBytes.toString('utf-8');
|
|
83
|
+
|
|
84
|
+
// signalis-core: tira error
|
|
85
|
+
Utf8.decode(badBytes); // → RangeError
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Casos de uso de Android x86_64
|
|
89
|
+
|
|
90
|
+
- **Android Emulator** (Android Studio) — el emulador corre x86_64, así que devs testeando apps ahora obtienen binarios nativos automáticamente
|
|
91
|
+
- **Termux en Chromebooks / tablets x86** — `pkg install nodejs && npm install @brashkie/signalis-core`
|
|
92
|
+
- **NodeJS-on-Android** con device farms x86
|
|
93
|
+
|
|
94
|
+
**Total de plataformas soportadas ahora: 10** (era 9 en v0.3.1).
|
|
95
|
+
|
|
96
|
+
Ver [CHANGELOG.md](./CHANGELOG.md) para todos los detalles.
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
35
100
|
## 🎉 Novedades en v0.3.0
|
|
36
101
|
|
|
37
102
|
**v0.3.0 trae soporte Android + ChaCha20-Poly1305 — totalmente retrocompatible con v0.2.0.**
|
|
@@ -83,7 +148,7 @@ Ver [MIGRATION.md](./MIGRATION.md) para detalles de upgrade (es un drop-in repla
|
|
|
83
148
|
|
|
84
149
|
## 🌍 Plataformas Soportadas
|
|
85
150
|
|
|
86
|
-
`@brashkie/signalis-core` envía binarios nativos pre-compilados para **
|
|
151
|
+
`@brashkie/signalis-core` envía binarios nativos pre-compilados para **10 plataformas** vía `optionalDependencies` de npm. El binario correcto se descarga automáticamente según tu sistema operativo + arquitectura.
|
|
87
152
|
|
|
88
153
|
| SO | Arquitectura | Sub-paquete | Estado |
|
|
89
154
|
|----|--------------|-------------|--------|
|
|
@@ -96,13 +161,16 @@ Ver [MIGRATION.md](./MIGRATION.md) para detalles de upgrade (es un drop-in repla
|
|
|
96
161
|
| 🪟 Windows | arm64 | `signalis-core-win32-arm64-msvc` | ✅ |
|
|
97
162
|
| 🤖 Android | arm64-v8a | `signalis-core-android-arm64` | 🆕 v0.3.0 |
|
|
98
163
|
| 🤖 Android | armv7 | `signalis-core-android-arm-eabi` | 🆕 v0.3.0 |
|
|
164
|
+
| 🤖 Android | x86_64 | `signalis-core-android-x64` | 🆕 v0.4.0 |
|
|
99
165
|
|
|
100
|
-
Próximas plataformas
|
|
166
|
+
Próximas plataformas: **WASM (navegadores)**, **iOS arm64**, **FreeBSD x64**, **RISC-V**.
|
|
101
167
|
|
|
102
168
|
### Instalación en Android
|
|
103
169
|
|
|
104
170
|
El mismo `npm install` funciona en cualquier entorno Node.js corriendo en Android, incluyendo:
|
|
105
171
|
- **Termux** en celulares Android (`pkg install nodejs`)
|
|
172
|
+
- **Termux en Chromebooks / tablets x86** → usa el binario `android-x64` automáticamente
|
|
173
|
+
- **Android Emulator** (Android Studio) → host x86_64, usa el binario `android-x64`
|
|
106
174
|
- **React Native** con target Android
|
|
107
175
|
- Apps con **NodeJS-Mobile**
|
|
108
176
|
- Builds personalizados de Node embebido para IoT
|
|
@@ -111,7 +179,7 @@ El mismo `npm install` funciona en cualquier entorno Node.js corriendo en Androi
|
|
|
111
179
|
# En Android (Termux por ejemplo):
|
|
112
180
|
pkg install nodejs
|
|
113
181
|
npm install @brashkie/signalis-core
|
|
114
|
-
# → npm descarga automáticamente
|
|
182
|
+
# → npm descarga automáticamente el sub-paquete correcto para tu arquitectura
|
|
115
183
|
```
|
|
116
184
|
|
|
117
185
|
---
|
|
@@ -148,7 +216,7 @@ npm install @brashkie/signalis-core
|
|
|
148
216
|
| 📦 **Paquete Dual** | Funciona en proyectos CommonJS, ESM y TypeScript |
|
|
149
217
|
| 🎯 **Tipado Estricto** | Definiciones TypeScript completas con tipos branded y clases de error |
|
|
150
218
|
| ✅ **Vectores de Test** | Validado contra RFC 5869, RFC 7748, RFC 8032, RFC 4231 y vectores NIST |
|
|
151
|
-
| 🌍 **Multi-Plataforma** | Binarios pre-compilados para Windows, macOS, Linux (x64, ARM) + **Android arm64/armv7** |
|
|
219
|
+
| 🌍 **Multi-Plataforma** | Binarios pre-compilados para Windows, macOS, Linux (x64, ARM) + **Android arm64/armv7/x86_64** |
|
|
152
220
|
| 🔒 **Tiempo Constante** | Comparaciones resistentes a side-channel via crate `subtle` |
|
|
153
221
|
| 🧹 **Auto-Borrado** | Secretos se borran de memoria automáticamente |
|
|
154
222
|
| 📊 **Cobertura 99%+** | Suite de tests con 269+ aserciones |
|
|
@@ -498,6 +566,44 @@ ChaCha20Poly1305.TAG_SIZE; // 16 (anexado al ciphertext)
|
|
|
498
566
|
|
|
499
567
|
**Seguridad:** ChaCha20-Poly1305 y AES-GCM tienen propiedades de seguridad equivalentes. La decisión es puramente sobre rendimiento en tu target.
|
|
500
568
|
|
|
569
|
+
### Base64 / Hex / UTF-8 🆕
|
|
570
|
+
|
|
571
|
+
Helpers nativos (Rust-side) de codificación introducidos en v0.4.0. Respaldados por el crate `sc-encoding`, que usa el ecosistema RustCrypto auditado (crates `base64` y `hex`) más la validación UTF-8 estricta integrada de Rust.
|
|
572
|
+
|
|
573
|
+
**¿Por qué no simplemente usar `Buffer.toString('base64')` de Node?**
|
|
574
|
+
- `Buffer.toString('utf-8')` sustituye silenciosamente `U+FFFD` en bytes inválidos. El nuestro tira error — mejor para verificación de firmas, parsing forense, input adversarial.
|
|
575
|
+
- Superficie de error consistente en toda la librería (mismo shape de `RangeError` que las primitivas criptográficas).
|
|
576
|
+
- Misma superficie de auditoría — un crate para revisar, no "lo que sea que Node envíe".
|
|
577
|
+
|
|
578
|
+
```typescript
|
|
579
|
+
import { Base64, Hex, Utf8 } from '@brashkie/signalis-core';
|
|
580
|
+
|
|
581
|
+
// ─── Base64 (estándar, RFC 4648) ───────────────────────────────────
|
|
582
|
+
Base64.encode(Buffer.from('hola')); // "aG9sYQ=="
|
|
583
|
+
Base64.decode('aG9sYQ=='); // <Buffer 68 6f 6c 61>
|
|
584
|
+
|
|
585
|
+
// ─── Base64 URL-safe (sin padding, sin + ni /) ─────────────────────
|
|
586
|
+
Base64.encodeUrlSafe(Buffer.from('hola')); // "aG9sYQ"
|
|
587
|
+
Base64.decodeUrlSafe('aG9sYQ');
|
|
588
|
+
|
|
589
|
+
// ─── Hex ────────────────────────────────────────────────────────────
|
|
590
|
+
Hex.encode(Buffer.from([0xde, 0xad])); // "dead"
|
|
591
|
+
Hex.encodeUpper(Buffer.from([0xde, 0xad])); // "DEAD"
|
|
592
|
+
Hex.decode('DEADBEEF'); // case-insensitive
|
|
593
|
+
Hex.isValid('deadbeef'); // true
|
|
594
|
+
Hex.isValid('nope'); // false — chars inválidos
|
|
595
|
+
|
|
596
|
+
// ─── UTF-8 (estricto) ───────────────────────────────────────────────
|
|
597
|
+
Utf8.encode('Hola 🦀'); // Buffer con 9 bytes
|
|
598
|
+
Utf8.decode(Buffer.from([0xc3, 0xb1])); // "ñ"
|
|
599
|
+
Utf8.decode(Buffer.from([0xff])); // → tira RangeError
|
|
600
|
+
Utf8.isValid(Buffer.from([0xff])); // false (no throw)
|
|
601
|
+
```
|
|
602
|
+
|
|
603
|
+
**Errores:** Cada operación de decode tira `RangeError` (subclase de `Error`) con mensaje descriptivo. Caracteres inválidos, longitudes incorrectas, padding malformado y secuencias UTF-8 inválidas siguen el mismo patrón.
|
|
604
|
+
|
|
605
|
+
**Rendimiento:** Estas funciones tienen un hop NAPI cada una. Para strings chicos el overhead es despreciable; para hot loops (millones de ops/seg en inputs pequeños) preferí `Buffer.toString()` del built-in de Node y recurrí a `Utf8.decode` solo cuando necesitás validación estricta.
|
|
606
|
+
|
|
501
607
|
### HMAC-SHA256
|
|
502
608
|
|
|
503
609
|
Autenticación de mensajes.
|
package/README.md
CHANGED
|
@@ -32,6 +32,71 @@ 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.4.0
|
|
36
|
+
|
|
37
|
+
**v0.4.0 ships native encoding helpers (Base64, Hex, UTF-8) + Android x86_64 target — fully backwards compatible with v0.3.x.**
|
|
38
|
+
|
|
39
|
+
| New | Description |
|
|
40
|
+
|-----|-------------|
|
|
41
|
+
| 🆕 **`Base64` namespace** | RFC 4648 standard + URL-safe variant (no padding), Rust-side implementation |
|
|
42
|
+
| 🆕 **`Hex` namespace** | Lowercase/uppercase encode, case-insensitive decode, `isValid()` cheap check |
|
|
43
|
+
| 🆕 **`Utf8` namespace** | **Strict** UTF-8 validation — throws on malformed bytes (unlike `Buffer.toString('utf-8')` which silently substitutes U+FFFD) |
|
|
44
|
+
| 🆕 **Android x86_64** | Native binary for the Android Emulator + Chromebooks + x86 tablets |
|
|
45
|
+
| 🆕 **`sc-encoding` crate** | Dedicated Rust crate — separates encoding logic from crypto for easier audit |
|
|
46
|
+
|
|
47
|
+
### Quick Example: Base64 / Hex / Utf8
|
|
48
|
+
|
|
49
|
+
```ts
|
|
50
|
+
import { Base64, Hex, Utf8 } from '@brashkie/signalis-core';
|
|
51
|
+
|
|
52
|
+
// UTF-8 with strict validation
|
|
53
|
+
const bytes = Utf8.encode('Hola 🦀'); // Buffer <48 6f 6c 61 20 f0 9f a6 80>
|
|
54
|
+
const roundTrip = Utf8.decode(bytes); // "Hola 🦀"
|
|
55
|
+
|
|
56
|
+
// Base64 standard (with padding)
|
|
57
|
+
const b64 = Base64.encode(bytes); // "SG9sYSDwn6aA"
|
|
58
|
+
const decoded = Base64.decode(b64); // matches original
|
|
59
|
+
|
|
60
|
+
// Base64 URL-safe (no padding, no + or /)
|
|
61
|
+
const urlSafe = Base64.encodeUrlSafe(bytes); // safe in URLs, filenames, JWTs
|
|
62
|
+
|
|
63
|
+
// Hex
|
|
64
|
+
const hex = Hex.encode(bytes); // "486f6c6120f09fa680"
|
|
65
|
+
const hexUpper = Hex.encodeUpper(bytes); // "486F6C6120F09FA680"
|
|
66
|
+
const back = Hex.decode('DEADBEEF'); // case-insensitive
|
|
67
|
+
Hex.isValid('deadbeef'); // true
|
|
68
|
+
Hex.isValid('nope!'); // false
|
|
69
|
+
|
|
70
|
+
// UTF-8 validation (cheap, no allocation)
|
|
71
|
+
Utf8.isValid(Buffer.from([0xff])); // false (0xff is never valid UTF-8)
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Why Strict UTF-8 Validation Matters
|
|
75
|
+
|
|
76
|
+
Node's built-in `Buffer.toString('utf-8')` **silently substitutes U+FFFD** (the replacement character) when it encounters invalid bytes. For most app code that's fine. But when you're processing signed messages, verifying MACs, or handling attacker-supplied input, **you want to know** if bytes are malformed rather than continue with silently-corrupted data.
|
|
77
|
+
|
|
78
|
+
```ts
|
|
79
|
+
const badBytes = Buffer.from([0xff, 0xfe]);
|
|
80
|
+
|
|
81
|
+
// Node's built-in: no error, produces "\uFFFD\uFFFD"
|
|
82
|
+
badBytes.toString('utf-8');
|
|
83
|
+
|
|
84
|
+
// signalis-core: throws
|
|
85
|
+
Utf8.decode(badBytes); // → RangeError
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Android x86_64 Use Cases
|
|
89
|
+
|
|
90
|
+
- **Android Emulator** (Android Studio) — emulator runs x86_64, so devs testing apps now get native binaries automatically
|
|
91
|
+
- **Termux on Chromebooks / x86 tablets** — `pkg install nodejs && npm install @brashkie/signalis-core`
|
|
92
|
+
- **NodeJS-on-Android** with x86 device farms
|
|
93
|
+
|
|
94
|
+
**Total platforms supported now: 10** (was 9 in v0.3.1).
|
|
95
|
+
|
|
96
|
+
See [CHANGELOG.md](./CHANGELOG.md) for full details.
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
35
100
|
## 🎉 What's New in v0.3.0
|
|
36
101
|
|
|
37
102
|
**v0.3.0 ships Android support + ChaCha20-Poly1305 — fully backwards compatible with v0.2.0.**
|
|
@@ -83,7 +148,7 @@ See [MIGRATION.md](./MIGRATION.md) for upgrade details (it's a drop-in replaceme
|
|
|
83
148
|
|
|
84
149
|
## 🌍 Supported Platforms
|
|
85
150
|
|
|
86
|
-
`@brashkie/signalis-core` ships prebuilt native binaries for **
|
|
151
|
+
`@brashkie/signalis-core` ships prebuilt native binaries for **10 platforms** via npm `optionalDependencies`. The right binary downloads automatically based on your host OS + arch.
|
|
87
152
|
|
|
88
153
|
| OS | Architecture | Sub-package | Status |
|
|
89
154
|
|----|--------------|-------------|--------|
|
|
@@ -96,13 +161,16 @@ See [MIGRATION.md](./MIGRATION.md) for upgrade details (it's a drop-in replaceme
|
|
|
96
161
|
| 🪟 Windows | arm64 | `signalis-core-win32-arm64-msvc` | ✅ |
|
|
97
162
|
| 🤖 Android | arm64-v8a | `signalis-core-android-arm64` | 🆕 v0.3.0 |
|
|
98
163
|
| 🤖 Android | armv7 | `signalis-core-android-arm-eabi` | 🆕 v0.3.0 |
|
|
164
|
+
| 🤖 Android | x86_64 | `signalis-core-android-x64` | 🆕 v0.4.0 |
|
|
99
165
|
|
|
100
|
-
Coming in
|
|
166
|
+
Coming in future releases: **WASM (browsers)**, **iOS arm64**, **FreeBSD x64**, **RISC-V**.
|
|
101
167
|
|
|
102
168
|
### Android Installation
|
|
103
169
|
|
|
104
170
|
Same `npm install` works in any Node.js environment running on Android, including:
|
|
105
171
|
- **Termux** on Android phones (`pkg install nodejs`)
|
|
172
|
+
- **Termux on Chromebooks / x86 tablets** → uses `android-x64` binary automatically
|
|
173
|
+
- **Android Emulator** (Android Studio) → x86_64 host, uses `android-x64` binary
|
|
106
174
|
- **React Native** with Android target
|
|
107
175
|
- **NodeJS-Mobile** apps
|
|
108
176
|
- Custom embedded Node builds for IoT
|
|
@@ -111,7 +179,7 @@ Same `npm install` works in any Node.js environment running on Android, includin
|
|
|
111
179
|
# On Android (Termux for example):
|
|
112
180
|
pkg install nodejs
|
|
113
181
|
npm install @brashkie/signalis-core
|
|
114
|
-
# → npm automatically downloads
|
|
182
|
+
# → npm automatically downloads the right sub-package for your arch
|
|
115
183
|
```
|
|
116
184
|
|
|
117
185
|
---
|
|
@@ -120,6 +188,7 @@ npm install @brashkie/signalis-core
|
|
|
120
188
|
|
|
121
189
|
- [🔐 Signalis Core](#-signalis-core)
|
|
122
190
|
- [✨ What is Signalis Core?](#-what-is-signalis-core)
|
|
191
|
+
- [🎉 What's New in v0.4.0](#-whats-new-in-v040)
|
|
123
192
|
- [🎉 What's New in v0.3.0](#-whats-new-in-v030)
|
|
124
193
|
- [🌍 Supported Platforms](#-supported-platforms)
|
|
125
194
|
- [📋 Table of Contents](#-table-of-contents)
|
|
@@ -178,7 +247,7 @@ npm install @brashkie/signalis-core
|
|
|
178
247
|
| 📦 **Dual Package** | Works in CommonJS, ESM, and TypeScript projects |
|
|
179
248
|
| 🎯 **Type-Safe** | Full TypeScript definitions with branded types and rich error classes |
|
|
180
249
|
| ✅ **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** |
|
|
250
|
+
| 🌍 **Cross-Platform** | Prebuilt binaries for Windows, macOS, Linux (x64, ARM) + **Android arm64/armv7/x86_64** |
|
|
182
251
|
| 🔒 **Constant-Time** | Side-channel resistant comparisons via `subtle` crate |
|
|
183
252
|
| 🧹 **Auto-Zeroization** | Secrets are wiped from memory automatically |
|
|
184
253
|
| 📊 **99%+ Coverage** | Comprehensive test suite with 269+ assertions |
|
|
@@ -532,6 +601,44 @@ ChaCha20Poly1305.TAG_SIZE; // 16 (appended to ciphertext)
|
|
|
532
601
|
|
|
533
602
|
**Security:** ChaCha20-Poly1305 and AES-GCM have equivalent security properties. The choice is purely about performance on your target.
|
|
534
603
|
|
|
604
|
+
### Base64 / Hex / UTF-8 🆕
|
|
605
|
+
|
|
606
|
+
Native (Rust-side) encoding helpers introduced in v0.4.0. Backed by the `sc-encoding` crate, which uses the audited RustCrypto ecosystem (`base64` and `hex` crates) plus Rust's built-in strict UTF-8 validation.
|
|
607
|
+
|
|
608
|
+
**Why not just use Node's `Buffer.toString('base64')`?**
|
|
609
|
+
- `Buffer.toString('utf-8')` silently substitutes `U+FFFD` for invalid bytes. Ours throws — better for signature verification, forensic parsing, adversarial input.
|
|
610
|
+
- Consistent error surface across the whole library (same `RangeError` shape as the crypto primitives).
|
|
611
|
+
- Same audit surface — one crate to review, not "whatever Node happens to ship".
|
|
612
|
+
|
|
613
|
+
```typescript
|
|
614
|
+
import { Base64, Hex, Utf8 } from '@brashkie/signalis-core';
|
|
615
|
+
|
|
616
|
+
// ─── Base64 (standard, RFC 4648) ───────────────────────────────────
|
|
617
|
+
Base64.encode(Buffer.from('hello')); // "aGVsbG8="
|
|
618
|
+
Base64.decode('aGVsbG8='); // <Buffer 68 65 6c 6c 6f>
|
|
619
|
+
|
|
620
|
+
// ─── Base64 URL-safe (no padding, no + or /) ───────────────────────
|
|
621
|
+
Base64.encodeUrlSafe(Buffer.from('hello')); // "aGVsbG8"
|
|
622
|
+
Base64.decodeUrlSafe('aGVsbG8');
|
|
623
|
+
|
|
624
|
+
// ─── Hex ────────────────────────────────────────────────────────────
|
|
625
|
+
Hex.encode(Buffer.from([0xde, 0xad])); // "dead"
|
|
626
|
+
Hex.encodeUpper(Buffer.from([0xde, 0xad])); // "DEAD"
|
|
627
|
+
Hex.decode('DEADBEEF'); // case-insensitive
|
|
628
|
+
Hex.isValid('deadbeef'); // true
|
|
629
|
+
Hex.isValid('nope'); // false — invalid chars
|
|
630
|
+
|
|
631
|
+
// ─── UTF-8 (strict) ─────────────────────────────────────────────────
|
|
632
|
+
Utf8.encode('Hola 🦀'); // Buffer with 9 bytes
|
|
633
|
+
Utf8.decode(Buffer.from([0xc3, 0xb1])); // "ñ"
|
|
634
|
+
Utf8.decode(Buffer.from([0xff])); // → throws RangeError
|
|
635
|
+
Utf8.isValid(Buffer.from([0xff])); // false (no throw)
|
|
636
|
+
```
|
|
637
|
+
|
|
638
|
+
**Errors:** Every decode operation throws `RangeError` (subclass of `Error`) with a descriptive message. Invalid characters, wrong lengths, malformed padding, and invalid UTF-8 sequences all follow the same pattern.
|
|
639
|
+
|
|
640
|
+
**Performance:** These functions have one NAPI hop each. For small strings the overhead is negligible; for hot loops (millions of ops/sec on tiny inputs) prefer `Buffer.toString()` from Node's built-in and only reach for `Utf8.decode` when you need strict validation.
|
|
641
|
+
|
|
535
642
|
### HMAC-SHA256
|
|
536
643
|
|
|
537
644
|
Message authentication.
|
package/dist/index.cjs
CHANGED
|
@@ -40,6 +40,7 @@ __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
|
+
Base64: () => Base64,
|
|
43
44
|
CHACHA20_POLY1305_KEY_SIZE: () => CHACHA20_POLY1305_KEY_SIZE,
|
|
44
45
|
CHACHA20_POLY1305_NONCE_SIZE: () => CHACHA20_POLY1305_NONCE_SIZE,
|
|
45
46
|
CHACHA20_POLY1305_TAG_SIZE: () => CHACHA20_POLY1305_TAG_SIZE,
|
|
@@ -59,6 +60,7 @@ __export(index_exports, {
|
|
|
59
60
|
HKDF_PRK_SIZE: () => HKDF_PRK_SIZE,
|
|
60
61
|
HMAC: () => HMAC,
|
|
61
62
|
HMAC_SHA256_TAG_SIZE: () => HMAC_SHA256_TAG_SIZE,
|
|
63
|
+
Hex: () => Hex,
|
|
62
64
|
KeyDerivationError: () => KeyDerivationError,
|
|
63
65
|
LengthError: () => LengthError,
|
|
64
66
|
SHA256: () => SHA256,
|
|
@@ -66,6 +68,7 @@ __export(index_exports, {
|
|
|
66
68
|
SHA256_OUTPUT_SIZE: () => SHA256_OUTPUT_SIZE,
|
|
67
69
|
SignalisError: () => SignalisError,
|
|
68
70
|
SignatureError: () => SignatureError,
|
|
71
|
+
Utf8: () => Utf8,
|
|
69
72
|
VERSION: () => VERSION,
|
|
70
73
|
ValidationError: () => ValidationError,
|
|
71
74
|
XED25519_PRIVATE_KEY_SIZE: () => XED25519_PRIVATE_KEY_SIZE,
|
|
@@ -833,6 +836,123 @@ function nativeSecureRandom(size) {
|
|
|
833
836
|
}
|
|
834
837
|
return native.secureRandom(size);
|
|
835
838
|
}
|
|
839
|
+
var Base64 = Object.freeze({
|
|
840
|
+
/**
|
|
841
|
+
* Encode bytes to standard Base64 (with `=` padding).
|
|
842
|
+
*/
|
|
843
|
+
encode(input) {
|
|
844
|
+
if (!Buffer.isBuffer(input)) {
|
|
845
|
+
throw new TypeError("Base64.encode: input must be a Buffer");
|
|
846
|
+
}
|
|
847
|
+
return native.base64Encode(input);
|
|
848
|
+
},
|
|
849
|
+
/**
|
|
850
|
+
* Decode a standard Base64 string back to bytes.
|
|
851
|
+
*
|
|
852
|
+
* @throws {RangeError} on invalid characters, wrong length, or invalid padding.
|
|
853
|
+
*/
|
|
854
|
+
decode(input) {
|
|
855
|
+
if (typeof input !== "string") {
|
|
856
|
+
throw new TypeError("Base64.decode: input must be a string");
|
|
857
|
+
}
|
|
858
|
+
return native.base64Decode(input);
|
|
859
|
+
},
|
|
860
|
+
/**
|
|
861
|
+
* Encode bytes to URL-safe Base64 without padding.
|
|
862
|
+
* Uses `-` and `_` instead of `+` and `/`.
|
|
863
|
+
*/
|
|
864
|
+
encodeUrlSafe(input) {
|
|
865
|
+
if (!Buffer.isBuffer(input)) {
|
|
866
|
+
throw new TypeError("Base64.encodeUrlSafe: input must be a Buffer");
|
|
867
|
+
}
|
|
868
|
+
return native.base64EncodeUrlSafe(input);
|
|
869
|
+
},
|
|
870
|
+
/**
|
|
871
|
+
* Decode a URL-safe Base64 string (no padding) back to bytes.
|
|
872
|
+
*
|
|
873
|
+
* @throws {RangeError} on invalid characters or wrong length.
|
|
874
|
+
*/
|
|
875
|
+
decodeUrlSafe(input) {
|
|
876
|
+
if (typeof input !== "string") {
|
|
877
|
+
throw new TypeError("Base64.decodeUrlSafe: input must be a string");
|
|
878
|
+
}
|
|
879
|
+
return native.base64DecodeUrlSafe(input);
|
|
880
|
+
}
|
|
881
|
+
});
|
|
882
|
+
var Hex = Object.freeze({
|
|
883
|
+
/**
|
|
884
|
+
* Encode bytes to a lowercase hex string.
|
|
885
|
+
*/
|
|
886
|
+
encode(input) {
|
|
887
|
+
if (!Buffer.isBuffer(input)) {
|
|
888
|
+
throw new TypeError("Hex.encode: input must be a Buffer");
|
|
889
|
+
}
|
|
890
|
+
return native.hexEncode(input);
|
|
891
|
+
},
|
|
892
|
+
/**
|
|
893
|
+
* Encode bytes to an uppercase hex string.
|
|
894
|
+
* (Rare, but included for legacy protocols.)
|
|
895
|
+
*/
|
|
896
|
+
encodeUpper(input) {
|
|
897
|
+
if (!Buffer.isBuffer(input)) {
|
|
898
|
+
throw new TypeError("Hex.encodeUpper: input must be a Buffer");
|
|
899
|
+
}
|
|
900
|
+
return native.hexEncodeUpper(input);
|
|
901
|
+
},
|
|
902
|
+
/**
|
|
903
|
+
* Decode a hex string to bytes. Case-insensitive.
|
|
904
|
+
*
|
|
905
|
+
* @throws {RangeError} on odd-length input or non-hex characters.
|
|
906
|
+
*/
|
|
907
|
+
decode(input) {
|
|
908
|
+
if (typeof input !== "string") {
|
|
909
|
+
throw new TypeError("Hex.decode: input must be a string");
|
|
910
|
+
}
|
|
911
|
+
return native.hexDecode(input);
|
|
912
|
+
},
|
|
913
|
+
/**
|
|
914
|
+
* Cheap validation: is this a well-formed hex string?
|
|
915
|
+
*/
|
|
916
|
+
isValid(input) {
|
|
917
|
+
if (typeof input !== "string") {
|
|
918
|
+
throw new TypeError("Hex.isValid: input must be a string");
|
|
919
|
+
}
|
|
920
|
+
return native.hexIsValid(input);
|
|
921
|
+
}
|
|
922
|
+
});
|
|
923
|
+
var Utf8 = Object.freeze({
|
|
924
|
+
/**
|
|
925
|
+
* Encode a string to its UTF-8 byte representation.
|
|
926
|
+
*/
|
|
927
|
+
encode(input) {
|
|
928
|
+
if (typeof input !== "string") {
|
|
929
|
+
throw new TypeError("Utf8.encode: input must be a string");
|
|
930
|
+
}
|
|
931
|
+
return native.utf8Encode(input);
|
|
932
|
+
},
|
|
933
|
+
/**
|
|
934
|
+
* Decode UTF-8 bytes to a string.
|
|
935
|
+
*
|
|
936
|
+
* @throws {RangeError} on invalid UTF-8 (truncated multi-byte, lone surrogate,
|
|
937
|
+
* invalid start byte, etc.). Unlike `Buffer.toString('utf-8')`, this does
|
|
938
|
+
* NOT silently substitute U+FFFD.
|
|
939
|
+
*/
|
|
940
|
+
decode(input) {
|
|
941
|
+
if (!Buffer.isBuffer(input)) {
|
|
942
|
+
throw new TypeError("Utf8.decode: input must be a Buffer");
|
|
943
|
+
}
|
|
944
|
+
return native.utf8Decode(input);
|
|
945
|
+
},
|
|
946
|
+
/**
|
|
947
|
+
* Check whether the given bytes are valid UTF-8.
|
|
948
|
+
*/
|
|
949
|
+
isValid(input) {
|
|
950
|
+
if (!Buffer.isBuffer(input)) {
|
|
951
|
+
throw new TypeError("Utf8.isValid: input must be a Buffer");
|
|
952
|
+
}
|
|
953
|
+
return native.utf8IsValid(input);
|
|
954
|
+
}
|
|
955
|
+
});
|
|
836
956
|
|
|
837
957
|
// src/types.ts
|
|
838
958
|
function asPublicKey(buf) {
|
|
@@ -920,7 +1040,7 @@ function xor(a, b) {
|
|
|
920
1040
|
}
|
|
921
1041
|
|
|
922
1042
|
// src/index.ts
|
|
923
|
-
var VERSION = "0.
|
|
1043
|
+
var VERSION = "0.4.0";
|
|
924
1044
|
var SignalisCore = Object.freeze({
|
|
925
1045
|
// Crypto primitives
|
|
926
1046
|
Curve25519,
|
|
@@ -929,22 +1049,29 @@ var SignalisCore = Object.freeze({
|
|
|
929
1049
|
HKDF,
|
|
930
1050
|
AES_GCM,
|
|
931
1051
|
AES_CBC,
|
|
1052
|
+
ChaCha20Poly1305,
|
|
932
1053
|
HMAC,
|
|
933
1054
|
SHA256,
|
|
934
1055
|
// Random
|
|
935
1056
|
secureRandom: secureRandom2,
|
|
1057
|
+
nativeSecureRandom,
|
|
936
1058
|
randomNonce,
|
|
937
1059
|
randomIv,
|
|
938
1060
|
randomKey,
|
|
939
|
-
// Encoding
|
|
1061
|
+
// Encoding (JS-side legacy helpers — use Base64/Hex/Utf8 for native)
|
|
940
1062
|
toHex,
|
|
941
1063
|
fromHex,
|
|
942
1064
|
toBase64,
|
|
943
1065
|
fromBase64,
|
|
1066
|
+
// Encoding (NEW v0.4.0 — native, RFC-compliant)
|
|
1067
|
+
Base64,
|
|
1068
|
+
Hex,
|
|
1069
|
+
Utf8,
|
|
944
1070
|
// Security
|
|
945
1071
|
constantTimeEqual,
|
|
1072
|
+
constantTimeEq: constantTimeEq2,
|
|
946
1073
|
// Version
|
|
947
|
-
VERSION
|
|
1074
|
+
VERSION,
|
|
948
1075
|
nativeVersion
|
|
949
1076
|
});
|
|
950
1077
|
var index_default = SignalisCore;
|
|
@@ -960,6 +1087,7 @@ var index_default = SignalisCore;
|
|
|
960
1087
|
AES_GCM_MAX_PLAINTEXT_SIZE,
|
|
961
1088
|
AES_GCM_RECOMMENDED_MESSAGES_PER_KEY,
|
|
962
1089
|
AuthenticationError,
|
|
1090
|
+
Base64,
|
|
963
1091
|
CHACHA20_POLY1305_KEY_SIZE,
|
|
964
1092
|
CHACHA20_POLY1305_NONCE_SIZE,
|
|
965
1093
|
CHACHA20_POLY1305_TAG_SIZE,
|
|
@@ -979,6 +1107,7 @@ var index_default = SignalisCore;
|
|
|
979
1107
|
HKDF_PRK_SIZE,
|
|
980
1108
|
HMAC,
|
|
981
1109
|
HMAC_SHA256_TAG_SIZE,
|
|
1110
|
+
Hex,
|
|
982
1111
|
KeyDerivationError,
|
|
983
1112
|
LengthError,
|
|
984
1113
|
SHA256,
|
|
@@ -986,6 +1115,7 @@ var index_default = SignalisCore;
|
|
|
986
1115
|
SHA256_OUTPUT_SIZE,
|
|
987
1116
|
SignalisError,
|
|
988
1117
|
SignatureError,
|
|
1118
|
+
Utf8,
|
|
989
1119
|
VERSION,
|
|
990
1120
|
ValidationError,
|
|
991
1121
|
XED25519_PRIVATE_KEY_SIZE,
|