@brashkie/signalis-core 0.1.0 β 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +110 -0
- package/MIGRATION.md +256 -0
- package/NOTICE +107 -9
- package/README.es.md +214 -19
- package/README.md +227 -22
- package/SECURITY.md +191 -0
- package/dist/index.cjs +214 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +183 -3
- package/dist/index.d.ts +183 -3
- package/dist/index.mjs +202 -3
- package/dist/index.mjs.map +1 -1
- package/index.d.ts +16 -0
- package/index.js +13 -1
- package/package.json +36 -24
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<div align="center">
|
|
2
2
|
|
|
3
|
-
<img src="
|
|
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
|
[](https://www.rust-lang.org/)
|
|
14
14
|
[](https://nodejs.org/)
|
|
15
15
|
[](#testing)
|
|
16
|
-
[](#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,27 @@ 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.2.0
|
|
36
|
+
|
|
37
|
+
**v0.2.0 introduces digital signatures and AAD authenticated encryption β fully backwards compatible with v0.1.0.**
|
|
38
|
+
|
|
39
|
+
| New | Description |
|
|
40
|
+
|-----|-------------|
|
|
41
|
+
| π **Ed25519** | Standard digital signatures (RFC 8032) β deterministic |
|
|
42
|
+
| π **XEd25519** | Signal-style signing with Curve25519 keys β one identity key for ECDH + signing |
|
|
43
|
+
| π **AES-GCM with AAD** | Additional Authenticated Data for binding metadata to ciphertext |
|
|
44
|
+
| π **SignatureError** | New typed error class for signature failures |
|
|
45
|
+
| π **Signature type** | Branded type with `asSignature()` helper |
|
|
46
|
+
|
|
47
|
+
See [MIGRATION.md](./MIGRATION.md) for upgrade details (it's a drop-in replacement).
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
35
51
|
## π Table of Contents
|
|
36
52
|
|
|
37
53
|
- [π Signalis Core](#-signalis-core)
|
|
38
54
|
- [β¨ What is Signalis Core?](#-what-is-signalis-core)
|
|
55
|
+
- [π What's New in v0.2.0](#-whats-new-in-v020)
|
|
39
56
|
- [π Table of Contents](#-table-of-contents)
|
|
40
57
|
- [π Features](#-features)
|
|
41
58
|
- [π€ Why Signalis Core?](#-why-signalis-core)
|
|
@@ -49,6 +66,8 @@ Built with **Rust** for safety and speed, exposed to Node.js via [napi-rs](https
|
|
|
49
66
|
- [Default Import](#default-import)
|
|
50
67
|
- [π API Reference](#-api-reference)
|
|
51
68
|
- [Curve25519 / X25519](#curve25519--x25519)
|
|
69
|
+
- [Ed25519](#ed25519)
|
|
70
|
+
- [XEd25519](#xed25519)
|
|
52
71
|
- [HKDF-SHA256](#hkdf-sha256)
|
|
53
72
|
- [AES-256-GCM](#aes-256-gcm)
|
|
54
73
|
- [AES-256-CBC](#aes-256-cbc)
|
|
@@ -59,6 +78,8 @@ Built with **Rust** for safety and speed, exposed to Node.js via [napi-rs](https
|
|
|
59
78
|
- [π‘ Examples](#-examples)
|
|
60
79
|
- [Example: Secure file encryption](#example-secure-file-encryption)
|
|
61
80
|
- [Example: Signal-style "Triple DH"](#example-signal-style-triple-dh)
|
|
81
|
+
- [Example: Signing identity assertions (NEW v0.2.0)](#example-signing-identity-assertions-new-v020)
|
|
82
|
+
- [Example: Encrypted messages with authenticated headers (NEW v0.2.0)](#example-encrypted-messages-with-authenticated-headers-new-v020)
|
|
62
83
|
- [ποΈ Architecture](#οΈ-architecture)
|
|
63
84
|
- [Build Output](#build-output)
|
|
64
85
|
- [π‘οΈ Security](#οΈ-security)
|
|
@@ -82,14 +103,16 @@ Built with **Rust** for safety and speed, exposed to Node.js via [napi-rs](https
|
|
|
82
103
|
| Feature | Description |
|
|
83
104
|
|---------|-------------|
|
|
84
105
|
| π₯ **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 |
|
|
106
|
+
| π‘οΈ **Audited Crypto** | Built on `curve25519-dalek`, `ed25519-dalek`, RustCrypto suite β battle-tested libraries |
|
|
107
|
+
| βοΈ **Digital Signatures** | Ed25519 (RFC 8032) and XEd25519 (Signal-style) β **NEW v0.2.0** |
|
|
108
|
+
| π **AEAD with AAD** | AES-256-GCM with Additional Authenticated Data β **NEW v0.2.0** |
|
|
86
109
|
| π¦ **Dual Package** | Works in CommonJS, ESM, and TypeScript projects |
|
|
87
110
|
| π― **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 |
|
|
111
|
+
| β
**Test Vectors** | Validated against RFC 5869, RFC 7748, RFC 8032, RFC 4231, and NIST vectors |
|
|
89
112
|
| π **Cross-Platform** | Prebuilt binaries for Windows, macOS, Linux (x64, ARM) |
|
|
90
113
|
| π **Constant-Time** | Side-channel resistant comparisons via `subtle` crate |
|
|
91
114
|
| π§Ή **Auto-Zeroization** | Secrets are wiped from memory automatically |
|
|
92
|
-
| π **99%+ Coverage** | Comprehensive test suite with
|
|
115
|
+
| π **99%+ Coverage** | Comprehensive test suite with 269+ assertions |
|
|
93
116
|
| π **Well Documented** | Complete JSDoc + inline examples for every function |
|
|
94
117
|
|
|
95
118
|
---
|
|
@@ -234,6 +257,86 @@ Curve25519.PUBLIC_KEY_SIZE; // 32
|
|
|
234
257
|
Curve25519.SHARED_SECRET_SIZE; // 32
|
|
235
258
|
```
|
|
236
259
|
|
|
260
|
+
### Ed25519
|
|
261
|
+
|
|
262
|
+
**NEW in v0.2.0.** Standard Ed25519 digital signatures (RFC 8032). Deterministic β same input always produces the same signature.
|
|
263
|
+
|
|
264
|
+
```typescript
|
|
265
|
+
import { Ed25519, type KeyPair, type Signature } from '@brashkie/signalis-core';
|
|
266
|
+
|
|
267
|
+
// Generate a new signing keypair
|
|
268
|
+
const keys: KeyPair = Ed25519.generateKeyPair();
|
|
269
|
+
// β { privateKey: Buffer(32), publicKey: Buffer(32) }
|
|
270
|
+
|
|
271
|
+
// Deterministic from a 32-byte seed
|
|
272
|
+
const fromSeed = Ed25519.keyPairFromSeed(seed);
|
|
273
|
+
|
|
274
|
+
// Derive public from private
|
|
275
|
+
const pub = Ed25519.publicFromPrivate(privateKey);
|
|
276
|
+
|
|
277
|
+
// Sign a message β 64-byte signature
|
|
278
|
+
const sig: Signature = Ed25519.sign(privateKey, message);
|
|
279
|
+
|
|
280
|
+
// Verify (throws SignatureError on failure)
|
|
281
|
+
Ed25519.verify(publicKey, message, sig);
|
|
282
|
+
|
|
283
|
+
// Verify (returns boolean, no throw)
|
|
284
|
+
const ok = Ed25519.verifyBool(publicKey, message, sig);
|
|
285
|
+
|
|
286
|
+
// Constants
|
|
287
|
+
Ed25519.PRIVATE_KEY_SIZE; // 32
|
|
288
|
+
Ed25519.PUBLIC_KEY_SIZE; // 32
|
|
289
|
+
Ed25519.SIGNATURE_SIZE; // 64
|
|
290
|
+
Ed25519.SEED_SIZE; // 32
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
### XEd25519
|
|
294
|
+
|
|
295
|
+
**NEW in v0.2.0.** Sign with the **SAME** Curve25519 keypair used for ECDH. This is what the Signal Protocol uses for identity keys.
|
|
296
|
+
|
|
297
|
+
```typescript
|
|
298
|
+
import { Curve25519, XEd25519 } from '@brashkie/signalis-core';
|
|
299
|
+
|
|
300
|
+
// ONE keypair for both ECDH and signing
|
|
301
|
+
const identity = Curve25519.generateKeyPair();
|
|
302
|
+
|
|
303
|
+
// Use for ECDH:
|
|
304
|
+
const shared = Curve25519.diffieHellman(identity.privateKey, peerPublic);
|
|
305
|
+
|
|
306
|
+
// Use the SAME key to sign:
|
|
307
|
+
const sig = XEd25519.sign(identity.privateKey, message);
|
|
308
|
+
|
|
309
|
+
// Verify with the SAME Curve25519 public key
|
|
310
|
+
XEd25519.verify(identity.publicKey, message, sig);
|
|
311
|
+
|
|
312
|
+
// XEd25519 signatures are NOT deterministic (use OS RNG)
|
|
313
|
+
const sig1 = XEd25519.sign(identity.privateKey, message);
|
|
314
|
+
const sig2 = XEd25519.sign(identity.privateKey, message);
|
|
315
|
+
// sig1.equals(sig2) β false (intentionally probabilistic)
|
|
316
|
+
|
|
317
|
+
// For deterministic signing (testing), provide explicit 64-byte random:
|
|
318
|
+
const random = secureRandom(64);
|
|
319
|
+
const detSig = XEd25519.signWithRandom(identity.privateKey, message, random);
|
|
320
|
+
|
|
321
|
+
// Verify (boolean, no throw)
|
|
322
|
+
const ok = XEd25519.verifyBool(identity.publicKey, message, sig);
|
|
323
|
+
|
|
324
|
+
// Constants
|
|
325
|
+
XEd25519.PRIVATE_KEY_SIZE; // 32 (same as Curve25519)
|
|
326
|
+
XEd25519.PUBLIC_KEY_SIZE; // 32 (same as Curve25519)
|
|
327
|
+
XEd25519.SIGNATURE_SIZE; // 64
|
|
328
|
+
XEd25519.RANDOM_SIZE; // 64
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
**When to use Ed25519 vs XEd25519:**
|
|
332
|
+
|
|
333
|
+
| Need | Use |
|
|
334
|
+
|------|-----|
|
|
335
|
+
| Standard Ed25519, deterministic, RFC 8032 compliant | **Ed25519** |
|
|
336
|
+
| Single identity key for ECDH + signing (Signal style) | **XEd25519** |
|
|
337
|
+
| Reproducible signatures from seed | **Ed25519** |
|
|
338
|
+
| Compatibility with Signal Protocol semantics | **XEd25519** |
|
|
339
|
+
|
|
237
340
|
### HKDF-SHA256
|
|
238
341
|
|
|
239
342
|
Key derivation per RFC 5869.
|
|
@@ -283,6 +386,26 @@ const pt = AES_GCM.decrypt(key, nonce, ct);
|
|
|
283
386
|
// Throws AuthenticationError if tampered
|
|
284
387
|
```
|
|
285
388
|
|
|
389
|
+
**With Additional Authenticated Data (AAD) β NEW in v0.2.0:**
|
|
390
|
+
|
|
391
|
+
```typescript
|
|
392
|
+
import { AES_GCM } from '@brashkie/signalis-core';
|
|
393
|
+
|
|
394
|
+
// AAD is authenticated but NOT encrypted β useful for headers/metadata
|
|
395
|
+
const header = Buffer.from('msg_id=42|sender=alice');
|
|
396
|
+
const body = Buffer.from('encrypted body content');
|
|
397
|
+
|
|
398
|
+
const ct = AES_GCM.encryptWithAad(key, nonce, body, header);
|
|
399
|
+
|
|
400
|
+
// Decrypt β MUST pass same AAD, or AuthenticationError
|
|
401
|
+
const pt = AES_GCM.decryptWithAad(key, nonce, ct, header);
|
|
402
|
+
|
|
403
|
+
// Tampering with header (AAD) β fails
|
|
404
|
+
const tampered = Buffer.from(header);
|
|
405
|
+
tampered[0] ^= 0xff;
|
|
406
|
+
AES_GCM.decryptWithAad(key, nonce, ct, tampered); // throws AuthenticationError
|
|
407
|
+
```
|
|
408
|
+
|
|
286
409
|
> **β οΈ 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
410
|
|
|
288
411
|
### AES-256-CBC
|
|
@@ -364,6 +487,7 @@ import {
|
|
|
364
487
|
CryptoError, // Crypto op failed
|
|
365
488
|
AuthenticationError, // Tag/MAC verification failed (extends CryptoError)
|
|
366
489
|
KeyDerivationError, // HKDF or similar failed (extends CryptoError)
|
|
490
|
+
SignatureError, // Ed25519/XEd25519 verify failed (extends CryptoError) β NEW v0.2.0
|
|
367
491
|
LengthError, // Output length out of bounds (extends ValidationError)
|
|
368
492
|
} from '@brashkie/signalis-core';
|
|
369
493
|
|
|
@@ -374,6 +498,8 @@ try {
|
|
|
374
498
|
console.error('Tampering detected!');
|
|
375
499
|
} else if (e instanceof ValidationError) {
|
|
376
500
|
console.error(`Invalid parameter: ${e.parameter}`);
|
|
501
|
+
} else if (e instanceof SignatureError) {
|
|
502
|
+
console.error('Invalid signature!');
|
|
377
503
|
}
|
|
378
504
|
}
|
|
379
505
|
```
|
|
@@ -385,10 +511,13 @@ try {
|
|
|
385
511
|
The `examples/` directory contains complete working demos:
|
|
386
512
|
|
|
387
513
|
```bash
|
|
388
|
-
npm run example:cjs
|
|
389
|
-
npm run example:esm
|
|
390
|
-
npm run example:ts
|
|
391
|
-
npm run
|
|
514
|
+
npm run example:cjs # CommonJS (10 demos)
|
|
515
|
+
npm run example:esm # ESM (Alice β Bob channel)
|
|
516
|
+
npm run example:ts # TypeScript (type-safe patterns)
|
|
517
|
+
npm run example:signing # Ed25519 + XEd25519 β NEW v0.2.0
|
|
518
|
+
npm run example:aad # AES-GCM with AAD β NEW v0.2.0
|
|
519
|
+
npm run example:e2e # Complete E2E channel β NEW v0.2.0
|
|
520
|
+
npm run examples # Run all
|
|
392
521
|
```
|
|
393
522
|
|
|
394
523
|
### Example: Secure file encryption
|
|
@@ -438,6 +567,66 @@ function tripleDH(IK_A_priv: Buffer, EK_A_priv: Buffer,
|
|
|
438
567
|
}
|
|
439
568
|
```
|
|
440
569
|
|
|
570
|
+
### Example: Signing identity assertions (NEW v0.2.0)
|
|
571
|
+
|
|
572
|
+
```typescript
|
|
573
|
+
import { Curve25519, XEd25519 } from '@brashkie/signalis-core';
|
|
574
|
+
|
|
575
|
+
// Alice's long-term identity key (Curve25519/XEd25519)
|
|
576
|
+
const aliceIdentity = Curve25519.generateKeyPair();
|
|
577
|
+
|
|
578
|
+
// Alice generates an ephemeral session key
|
|
579
|
+
const aliceEphemeral = Curve25519.generateKeyPair();
|
|
580
|
+
|
|
581
|
+
// Alice signs her ephemeral with her identity β proves "this ephemeral is mine"
|
|
582
|
+
const authProof = XEd25519.sign(
|
|
583
|
+
aliceIdentity.privateKey,
|
|
584
|
+
aliceEphemeral.publicKey,
|
|
585
|
+
);
|
|
586
|
+
|
|
587
|
+
// Bob verifies the authorization:
|
|
588
|
+
// 1. Got Alice's identity public key from a trusted source
|
|
589
|
+
// 2. Receives ephemeral + signature
|
|
590
|
+
try {
|
|
591
|
+
XEd25519.verify(aliceIdentity.publicKey, aliceEphemeral.publicKey, authProof);
|
|
592
|
+
// β
Bob now trusts the ephemeral key belongs to Alice
|
|
593
|
+
} catch {
|
|
594
|
+
// β Mallory tried to MITM with her own ephemeral
|
|
595
|
+
}
|
|
596
|
+
```
|
|
597
|
+
|
|
598
|
+
### Example: Encrypted messages with authenticated headers (NEW v0.2.0)
|
|
599
|
+
|
|
600
|
+
```typescript
|
|
601
|
+
import { AES_GCM, secureRandom } from '@brashkie/signalis-core';
|
|
602
|
+
|
|
603
|
+
const sessionKey = derivedFromECDHandHKDF;
|
|
604
|
+
|
|
605
|
+
function sendMessage(body: Buffer, msgId: number) {
|
|
606
|
+
const nonce = secureRandom(12);
|
|
607
|
+
const header = Buffer.from(JSON.stringify({
|
|
608
|
+
msg_id: msgId,
|
|
609
|
+
timestamp: Date.now(),
|
|
610
|
+
sender: 'alice',
|
|
611
|
+
}));
|
|
612
|
+
|
|
613
|
+
// Header is authenticated but NOT encrypted (receiver needs it as plaintext)
|
|
614
|
+
const ciphertext = AES_GCM.encryptWithAad(sessionKey, nonce, body, header);
|
|
615
|
+
|
|
616
|
+
return { header, nonce, ciphertext };
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
function receiveMessage(packet: { header: Buffer; nonce: Buffer; ciphertext: Buffer }) {
|
|
620
|
+
// Decryption fails if EITHER ciphertext OR header was tampered
|
|
621
|
+
return AES_GCM.decryptWithAad(
|
|
622
|
+
sessionKey,
|
|
623
|
+
packet.nonce,
|
|
624
|
+
packet.ciphertext,
|
|
625
|
+
packet.header,
|
|
626
|
+
);
|
|
627
|
+
}
|
|
628
|
+
```
|
|
629
|
+
|
|
441
630
|
---
|
|
442
631
|
|
|
443
632
|
## ποΈ Architecture
|
|
@@ -445,18 +634,20 @@ function tripleDH(IK_A_priv: Buffer, EK_A_priv: Buffer,
|
|
|
445
634
|
```
|
|
446
635
|
@brashkie/signalis-core
|
|
447
636
|
β
|
|
448
|
-
βββ π¦ Rust Workspace (
|
|
637
|
+
βββ π¦ Rust Workspace (8 crates)
|
|
449
638
|
β βββ sc-curve25519 β X25519 ECDH operations
|
|
639
|
+
β βββ sc-ed25519 β Ed25519 signatures (RFC 8032) β NEW v0.2.0
|
|
640
|
+
β βββ sc-xed25519 β XEd25519 Signal-style signatures β NEW v0.2.0
|
|
450
641
|
β βββ sc-hkdf β HKDF-SHA256 derivation
|
|
451
|
-
β βββ sc-aes β AES-256-GCM & CBC
|
|
642
|
+
β βββ sc-aes β AES-256-GCM (with AAD) & CBC
|
|
452
643
|
β βββ sc-hmac β HMAC-SHA256 with constant-time verify
|
|
453
644
|
β βββ sc-sha256 β SHA-256 hashing
|
|
454
645
|
β βββ sc-node β NAPI-RS bindings (cdylib)
|
|
455
646
|
β
|
|
456
647
|
βββ π¦ TypeScript Layer
|
|
457
648
|
βββ core.ts β Crypto wrappers with validation
|
|
458
|
-
βββ types.ts β Type definitions (KeyPair, etc.)
|
|
459
|
-
βββ errors.ts β Typed error classes
|
|
649
|
+
βββ types.ts β Type definitions (KeyPair, Signature, etc.)
|
|
650
|
+
βββ errors.ts β Typed error classes (incl. SignatureError)
|
|
460
651
|
βββ validators.ts β Input assertions
|
|
461
652
|
βββ utils.ts β Encoding + random + buffer helpers
|
|
462
653
|
βββ constants.ts β Public constants (sizes, limits)
|
|
@@ -487,8 +678,10 @@ dist/
|
|
|
487
678
|
| Primitive | Spec | Implementation |
|
|
488
679
|
|-----------|------|----------------|
|
|
489
680
|
| X25519 | [RFC 7748](https://datatracker.ietf.org/doc/html/rfc7748) | [`curve25519-dalek`](https://github.com/dalek-cryptography/curve25519-dalek) (audited by NCC Group) |
|
|
681
|
+
| Ed25519 | [RFC 8032](https://datatracker.ietf.org/doc/html/rfc8032) | [`ed25519-dalek`](https://github.com/dalek-cryptography/ed25519-dalek) (audited) β **NEW v0.2.0** |
|
|
682
|
+
| XEd25519 | [Signal Spec](https://signal.org/docs/specifications/xeddsa/) | Custom impl over `curve25519-dalek` β **NEW v0.2.0** |
|
|
490
683
|
| 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) |
|
|
684
|
+
| 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
685
|
| 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
686
|
| HMAC-SHA256 | [RFC 2104](https://datatracker.ietf.org/doc/html/rfc2104) | [`hmac`](https://github.com/RustCrypto/MACs) (RustCrypto) |
|
|
494
687
|
| SHA-256 | [FIPS 180-4](https://csrc.nist.gov/publications/detail/fips/180/4/final) | [`sha2`](https://github.com/RustCrypto/hashes) (RustCrypto) |
|
|
@@ -521,12 +714,17 @@ Benchmarks (Node 22, x86_64):
|
|
|
521
714
|
|-----------|------------|-------------|
|
|
522
715
|
| Curve25519 keygen | ~50,000 ops/sec | **15Γ** faster than tweetnacl |
|
|
523
716
|
| X25519 ECDH | ~25,000 ops/sec | **20Γ** faster |
|
|
717
|
+
| Ed25519 sign | ~25,000 ops/sec | **20Γ** faster β NEW v0.2.0 |
|
|
718
|
+
| Ed25519 verify | ~10,000 ops/sec | **15Γ** faster β NEW v0.2.0 |
|
|
719
|
+
| XEd25519 sign | ~20,000 ops/sec | β β NEW v0.2.0 |
|
|
720
|
+
| XEd25519 verify | ~10,000 ops/sec | β β NEW v0.2.0 |
|
|
524
721
|
| HKDF derive (32 bytes) | ~500,000 ops/sec | **30Γ** faster |
|
|
525
722
|
| AES-256-GCM encrypt (1 KB) | ~2 GB/sec | **80Γ** faster |
|
|
723
|
+
| AES-GCM with AAD | <5% overhead vs no AAD | β NEW v0.2.0 |
|
|
526
724
|
| SHA-256 (1 KB) | ~3 GB/sec | **50Γ** faster |
|
|
527
725
|
| HMAC-SHA256 (1 KB) | ~2.5 GB/sec | **40Γ** faster |
|
|
528
726
|
|
|
529
|
-
*Run `npm run bench` to benchmark on your machine (coming in v0.
|
|
727
|
+
*Run `npm run bench` to benchmark on your machine (coming in v0.3).*
|
|
530
728
|
|
|
531
729
|
---
|
|
532
730
|
|
|
@@ -562,15 +760,20 @@ npm run test:coverage:ui
|
|
|
562
760
|
|
|
563
761
|
### What's Tested
|
|
564
762
|
|
|
565
|
-
- β
All RFC test vectors (RFC 5869, 7748, 4231)
|
|
763
|
+
- β
All RFC test vectors (RFC 5869, 7748, 8032, 4231)
|
|
566
764
|
- β
All NIST test vectors (AES, SHA-256)
|
|
765
|
+
- β
Ed25519 RFC 8032 vector 1 (empty message) β NEW v0.2.0
|
|
766
|
+
- β
XEd25519 round-trips with deterministic + probabilistic signing β NEW v0.2.0
|
|
767
|
+
- β
AES-GCM AAD authentication (tampered AAD fails decryption) β NEW v0.2.0
|
|
567
768
|
- β
Input validation for every public function
|
|
568
769
|
- β
Error handling for every code path
|
|
569
770
|
- β
Round-trip encryption / decryption
|
|
570
|
-
- β
Tampering detection (AES-GCM tag failures)
|
|
771
|
+
- β
Tampering detection (AES-GCM tag failures, signature failures)
|
|
571
772
|
- β
Both CommonJS and ESM consumption paths
|
|
572
773
|
- β
Default export and named exports
|
|
573
774
|
|
|
775
|
+
**Test count: 269+ assertions across Rust (48) + Vitest (172) + CJS (12) + ESM (15) + new v0.2.0 tests (49).**
|
|
776
|
+
|
|
574
777
|
---
|
|
575
778
|
|
|
576
779
|
## π¨ Building from Source
|
|
@@ -611,9 +814,10 @@ npm run examples
|
|
|
611
814
|
See [ROADMAP.md](./ROADMAP.md) for detailed plans.
|
|
612
815
|
|
|
613
816
|
**TL;DR:**
|
|
614
|
-
- **v0.1** β
β Cryptographic primitives (
|
|
615
|
-
- **v0.2** β
|
|
616
|
-
- **
|
|
817
|
+
- **v0.1** β
β Cryptographic primitives (Curve25519, HKDF, AES, HMAC, SHA-256)
|
|
818
|
+
- **v0.2** β
β Ed25519, XEd25519, AES-GCM with AAD (current release)
|
|
819
|
+
- **v0.3** β Benchmarks, X448 support, more test vectors
|
|
820
|
+
- **v1.0** β Stable API, external audit
|
|
617
821
|
- **Then:** [@brashkie/signalis](https://github.com/Brashkie/signalis) (X3DH + Double Ratchet)
|
|
618
822
|
- **Then:** [@brashkie/waproto](https://github.com/Brashkie/waproto) (WhatsApp Protocol)
|
|
619
823
|
- **Then:** HepeinBaileys 2.0 (full WhatsApp client from scratch)
|
|
@@ -638,9 +842,10 @@ Please also read our [Code of Conduct](./CODE_OF_CONDUCT.md).
|
|
|
638
842
|
Built on the shoulders of giants:
|
|
639
843
|
|
|
640
844
|
- **[curve25519-dalek](https://github.com/dalek-cryptography/curve25519-dalek)** β Curve25519 in pure Rust
|
|
845
|
+
- **[ed25519-dalek](https://github.com/dalek-cryptography/ed25519-dalek)** β Ed25519 in pure Rust (added in v0.2.0)
|
|
641
846
|
- **[RustCrypto](https://github.com/RustCrypto)** β `aes`, `hkdf`, `hmac`, `sha2`
|
|
642
847
|
- **[napi-rs](https://napi.rs)** β Rust β Node bindings
|
|
643
|
-
- **Signal Foundation** β [Protocol specifications](https://signal.org/docs/)
|
|
848
|
+
- **Signal Foundation** β [Protocol specifications](https://signal.org/docs/) (including [XEd25519](https://signal.org/docs/specifications/xeddsa/))
|
|
644
849
|
- **[tsup](https://tsup.egoist.dev/)** β Dual ESM/CJS bundler
|
|
645
850
|
- **[Vitest](https://vitest.dev/)** β Modern test runner
|
|
646
851
|
|
|
@@ -662,4 +867,4 @@ See [LICENSE](./LICENSE) and [NOTICE](./NOTICE) for full details.
|
|
|
662
867
|
|
|
663
868
|
[GitHub](https://github.com/Brashkie) Β· [npm](https://www.npmjs.com/~brashkie)
|
|
664
869
|
|
|
665
|
-
</div>
|
|
870
|
+
</div>
|
package/SECURITY.md
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
# π‘οΈ Security Policy
|
|
2
|
+
|
|
3
|
+
## Supported Versions
|
|
4
|
+
|
|
5
|
+
| Version | Supported | Status |
|
|
6
|
+
|---------|-----------|--------|
|
|
7
|
+
| 0.2.x | β
| Active development |
|
|
8
|
+
| 0.1.x | β οΈ | Security fixes only (until 2026-08-01) |
|
|
9
|
+
| < 0.1 | β | Not supported |
|
|
10
|
+
|
|
11
|
+
When a new minor version is released, the previous minor enters **3 months of security-only support**.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## π¨ Reporting a Vulnerability
|
|
16
|
+
|
|
17
|
+
**Please do NOT open public GitHub issues for security bugs.**
|
|
18
|
+
|
|
19
|
+
### Reporting Channels
|
|
20
|
+
|
|
21
|
+
Use one of these private channels:
|
|
22
|
+
|
|
23
|
+
1. **GitHub Security Advisories** (preferred):
|
|
24
|
+
- https://github.com/Brashkie/signalis-core/security/advisories/new
|
|
25
|
+
|
|
26
|
+
2. **Email**: brashkie@hepein.com
|
|
27
|
+
- PGP key: [download from GitHub profile]
|
|
28
|
+
- Subject: `[SECURITY] signalis-core: <brief description>`
|
|
29
|
+
|
|
30
|
+
### What to Include
|
|
31
|
+
|
|
32
|
+
To help us triage quickly, please provide:
|
|
33
|
+
|
|
34
|
+
- **Description** of the vulnerability
|
|
35
|
+
- **Affected versions** (if known)
|
|
36
|
+
- **Reproduction steps** (proof of concept welcome)
|
|
37
|
+
- **Impact assessment** (your view on severity)
|
|
38
|
+
- **Suggested fix** (if any)
|
|
39
|
+
- **Your contact info** (for follow-up)
|
|
40
|
+
|
|
41
|
+
### Response Timeline
|
|
42
|
+
|
|
43
|
+
| Severity | First response | Fix target |
|
|
44
|
+
|----------|---------------|------------|
|
|
45
|
+
| **Critical** (remote exploit, key recovery) | 24 hours | 7 days |
|
|
46
|
+
| **High** (timing leaks, DoS) | 3 days | 30 days |
|
|
47
|
+
| **Medium** (specific edge cases) | 7 days | Next minor release |
|
|
48
|
+
| **Low** (theoretical, hard to exploit) | 14 days | Best effort |
|
|
49
|
+
|
|
50
|
+
### Disclosure Process
|
|
51
|
+
|
|
52
|
+
1. We acknowledge receipt within the timeframes above
|
|
53
|
+
2. We investigate and confirm the issue
|
|
54
|
+
3. We develop a fix in a private branch
|
|
55
|
+
4. We prepare a security advisory
|
|
56
|
+
5. We coordinate a release (typically 7-30 days from confirmation)
|
|
57
|
+
6. **You are credited** in the advisory (unless you prefer anonymity)
|
|
58
|
+
|
|
59
|
+
### Bounty Program
|
|
60
|
+
|
|
61
|
+
We do **not** currently offer monetary bounties, but we:
|
|
62
|
+
- Publicly credit researchers (with permission)
|
|
63
|
+
- Provide signed acknowledgment letters
|
|
64
|
+
- Add you to the contributors hall of fame
|
|
65
|
+
|
|
66
|
+
If you're interested in funded research, please reach out.
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## π Security Considerations
|
|
71
|
+
|
|
72
|
+
### Cryptographic Implementation
|
|
73
|
+
|
|
74
|
+
All primitives use **audited, widely-deployed Rust crates**:
|
|
75
|
+
|
|
76
|
+
| Primitive | Crate | Last audited |
|
|
77
|
+
|-----------|-------|--------------|
|
|
78
|
+
| Curve25519 | `curve25519-dalek` 4.x | 2023 (Cure53) |
|
|
79
|
+
| Ed25519 | `ed25519-dalek` 2.x | 2023 (Cure53) |
|
|
80
|
+
| AES-GCM | `aes-gcm` (RustCrypto) | 2022 |
|
|
81
|
+
| SHA-256/HMAC | `sha2`, `hmac` (RustCrypto) | 2022 |
|
|
82
|
+
| HKDF | `hkdf` (RustCrypto) | 2022 |
|
|
83
|
+
|
|
84
|
+
We do **not** implement primitives ourselves. Our work is:
|
|
85
|
+
- Glue code (NAPI bindings)
|
|
86
|
+
- Validation logic
|
|
87
|
+
- Type safety wrappers
|
|
88
|
+
- Test infrastructure
|
|
89
|
+
|
|
90
|
+
### Side-Channel Resistance
|
|
91
|
+
|
|
92
|
+
The underlying Rust crates provide:
|
|
93
|
+
- β
**Constant-time** scalar multiplication (Curve25519, Ed25519)
|
|
94
|
+
- β
**Constant-time** equality checks (HMAC verify, point equality)
|
|
95
|
+
- β
**AES-NI** hardware acceleration (when available)
|
|
96
|
+
- β
**Zeroization** of private key buffers on drop
|
|
97
|
+
|
|
98
|
+
### Known Limitations
|
|
99
|
+
|
|
100
|
+
#### XEd25519 (v0.2.0)
|
|
101
|
+
|
|
102
|
+
XEd25519 implementation follows the [Signal specification](https://signal.org/docs/specifications/xeddsa/). While Signal has deployed this in production for years, our implementation is **new** and has not been independently audited.
|
|
103
|
+
|
|
104
|
+
**Mitigation:** We use `curve25519-dalek`'s low-level primitives and `ed25519-dalek`'s `hazmat` API, both of which ARE audited. Only the high-level XEd25519 logic is ours.
|
|
105
|
+
|
|
106
|
+
If you require audited XEd25519, consider waiting for v1.0.0 (audited release).
|
|
107
|
+
|
|
108
|
+
#### Random Number Generation
|
|
109
|
+
|
|
110
|
+
We use `OsRng` from the `rand` crate, which sources entropy from:
|
|
111
|
+
- **Linux**: `getrandom()` syscall
|
|
112
|
+
- **macOS**: `SecRandomCopyBytes`
|
|
113
|
+
- **Windows**: `BCryptGenRandom`
|
|
114
|
+
- **iOS**: same as macOS
|
|
115
|
+
|
|
116
|
+
These are the standard, recommended sources. However:
|
|
117
|
+
- π¨ **VMs with poor entropy** can produce weak keys
|
|
118
|
+
- π¨ **Hibernation/snapshots** can reuse RNG state
|
|
119
|
+
|
|
120
|
+
Mitigation: Generate keys after the system has accumulated entropy, especially in VM contexts.
|
|
121
|
+
|
|
122
|
+
### Memory Safety
|
|
123
|
+
|
|
124
|
+
- **Rust side**: Compile-time memory safety, zeroization on drop
|
|
125
|
+
- **JS/Node side**: Buffers can be GC'd at any time
|
|
126
|
+
|
|
127
|
+
If you need guaranteed key wiping in JS:
|
|
128
|
+
```typescript
|
|
129
|
+
// Explicit overwrite (best effort)
|
|
130
|
+
function wipeKey(buf: Buffer) {
|
|
131
|
+
buf.fill(0);
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
**Note:** Due to JS GC behavior, this is best-effort. For highest assurance, use HSM/TPM-backed keys.
|
|
136
|
+
|
|
137
|
+
### Network Security
|
|
138
|
+
|
|
139
|
+
This library provides **cryptographic primitives only**. It does NOT:
|
|
140
|
+
- β Establish network connections
|
|
141
|
+
- β Implement TLS/HTTPS
|
|
142
|
+
- β Validate certificates
|
|
143
|
+
- β Manage sessions
|
|
144
|
+
|
|
145
|
+
Your application is responsible for transport-layer security.
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## π« Out of Scope
|
|
150
|
+
|
|
151
|
+
The following are **not considered vulnerabilities**:
|
|
152
|
+
|
|
153
|
+
- π΅ **Performance issues** (open as feature request)
|
|
154
|
+
- π΅ **API ergonomics** (open as feature request)
|
|
155
|
+
- π΅ **Documentation typos** (open as PR)
|
|
156
|
+
- π΅ **Behavior on invalid inputs** that throws clear errors (working as intended)
|
|
157
|
+
- π΅ **Side-channels in user code** (your code's responsibility)
|
|
158
|
+
- π΅ **Issues in dependencies** (report upstream, but tell us too)
|
|
159
|
+
- π΅ **Hypothetical attacks** without practical demonstration
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## π Hall of Fame
|
|
164
|
+
|
|
165
|
+
We thank the following security researchers for responsible disclosure:
|
|
166
|
+
|
|
167
|
+
_(Empty for now β be the first!)_
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
## π Security Resources
|
|
172
|
+
|
|
173
|
+
For learning about cryptographic security:
|
|
174
|
+
|
|
175
|
+
- π [Cryptography Engineering](https://www.schneier.com/books/cryptography-engineering/) β Schneier, Ferguson, Kohno
|
|
176
|
+
- π [Real-World Cryptography](https://www.manning.com/books/real-world-cryptography) β David Wong
|
|
177
|
+
- π [Serious Cryptography](https://nostarch.com/seriouscrypto) β Jean-Philippe Aumasson
|
|
178
|
+
- π [Signal Protocol Documentation](https://signal.org/docs/)
|
|
179
|
+
- π [Cryptography Stack Exchange](https://crypto.stackexchange.com/)
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
## π Policy Updates
|
|
184
|
+
|
|
185
|
+
This policy may be updated. Significant changes will be:
|
|
186
|
+
1. Committed to the repo with a clear commit message
|
|
187
|
+
2. Announced in the next release notes
|
|
188
|
+
3. Posted in GitHub Discussions
|
|
189
|
+
|
|
190
|
+
**Last updated:** May 2026
|
|
191
|
+
**Next scheduled review:** November 2026
|