@brashkie/signalis-core 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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
package/dist/index.cjs CHANGED
@@ -40,11 +40,20 @@ __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,
52
+ ED25519_PRIVATE_KEY_SIZE: () => ED25519_PRIVATE_KEY_SIZE,
53
+ ED25519_PUBLIC_KEY_SIZE: () => ED25519_PUBLIC_KEY_SIZE,
54
+ ED25519_SEED_SIZE: () => ED25519_SEED_SIZE,
55
+ ED25519_SIGNATURE_SIZE: () => ED25519_SIGNATURE_SIZE,
56
+ Ed25519: () => Ed25519,
48
57
  HKDF: () => HKDF,
49
58
  HKDF_MAX_OUTPUT_SIZE: () => HKDF_MAX_OUTPUT_SIZE,
50
59
  HKDF_PRK_SIZE: () => HKDF_PRK_SIZE,
@@ -56,11 +65,18 @@ __export(index_exports, {
56
65
  SHA256_BLOCK_SIZE: () => SHA256_BLOCK_SIZE,
57
66
  SHA256_OUTPUT_SIZE: () => SHA256_OUTPUT_SIZE,
58
67
  SignalisError: () => SignalisError,
68
+ SignatureError: () => SignatureError,
59
69
  VERSION: () => VERSION,
60
70
  ValidationError: () => ValidationError,
71
+ XED25519_PRIVATE_KEY_SIZE: () => XED25519_PRIVATE_KEY_SIZE,
72
+ XED25519_PUBLIC_KEY_SIZE: () => XED25519_PUBLIC_KEY_SIZE,
73
+ XED25519_RANDOM_SIZE: () => XED25519_RANDOM_SIZE,
74
+ XED25519_SIGNATURE_SIZE: () => XED25519_SIGNATURE_SIZE,
75
+ XEd25519: () => XEd25519,
61
76
  asPrivateKey: () => asPrivateKey,
62
77
  asPublicKey: () => asPublicKey,
63
78
  asSharedSecret: () => asSharedSecret,
79
+ asSignature: () => asSignature,
64
80
  assertBuffer: () => assertBuffer,
65
81
  assertBufferLength: () => assertBufferLength,
66
82
  assertBufferOfSize: () => assertBufferOfSize,
@@ -69,16 +85,18 @@ __export(index_exports, {
69
85
  bufferToString: () => bufferToString,
70
86
  buffersSameLength: () => buffersSameLength,
71
87
  concat: () => concat,
88
+ constantTimeEq: () => constantTimeEq2,
72
89
  constantTimeEqual: () => constantTimeEqual,
73
90
  default: () => index_default,
74
91
  fromBase64: () => fromBase64,
75
92
  fromBase64Url: () => fromBase64Url,
76
93
  fromHex: () => fromHex,
94
+ nativeSecureRandom: () => nativeSecureRandom,
77
95
  nativeVersion: () => nativeVersion,
78
96
  randomIv: () => randomIv,
79
97
  randomKey: () => randomKey,
80
98
  randomNonce: () => randomNonce,
81
- secureRandom: () => secureRandom,
99
+ secureRandom: () => secureRandom2,
82
100
  stringToBuffer: () => stringToBuffer,
83
101
  toBase64: () => toBase64,
84
102
  toBase64Url: () => toBase64Url,
@@ -95,6 +113,14 @@ var native = __toESM(require("../index.js"));
95
113
  var CURVE25519_PRIVATE_KEY_SIZE = 32;
96
114
  var CURVE25519_PUBLIC_KEY_SIZE = 32;
97
115
  var CURVE25519_SHARED_SECRET_SIZE = 32;
116
+ var ED25519_PRIVATE_KEY_SIZE = 32;
117
+ var ED25519_PUBLIC_KEY_SIZE = 32;
118
+ var ED25519_SIGNATURE_SIZE = 64;
119
+ var ED25519_SEED_SIZE = 32;
120
+ var XED25519_PRIVATE_KEY_SIZE = 32;
121
+ var XED25519_PUBLIC_KEY_SIZE = 32;
122
+ var XED25519_SIGNATURE_SIZE = 64;
123
+ var XED25519_RANDOM_SIZE = 64;
98
124
  var HKDF_PRK_SIZE = 32;
99
125
  var HKDF_MAX_OUTPUT_SIZE = 8160;
100
126
  var AES_256_KEY_SIZE = 32;
@@ -157,6 +183,12 @@ var KeyDerivationError = class extends CryptoError {
157
183
  this.name = "KeyDerivationError";
158
184
  }
159
185
  };
186
+ var SignatureError = class extends CryptoError {
187
+ constructor(message = "Signature verification failed") {
188
+ super(message, "verify_signature");
189
+ this.name = "SignatureError";
190
+ }
191
+ };
160
192
  var LengthError = class extends ValidationError {
161
193
  constructor(message, options = {}) {
162
194
  super(message, options);
@@ -436,7 +468,53 @@ var AES_GCM = Object.freeze({
436
468
  /** Nonce size in bytes (12). */
437
469
  NONCE_SIZE: AES_256_GCM_NONCE_SIZE,
438
470
  /** Tag size in bytes (16). */
439
- TAG_SIZE: AES_256_GCM_TAG_SIZE
471
+ TAG_SIZE: AES_256_GCM_TAG_SIZE,
472
+ /**
473
+ * Encrypt with AES-256-GCM and Additional Authenticated Data (NEW in v0.2.0).
474
+ *
475
+ * AAD is authenticated but NOT encrypted. Useful for binding metadata
476
+ * (like message headers) to the ciphertext.
477
+ *
478
+ * @param key - 32-byte symmetric key
479
+ * @param nonce - 12-byte unique nonce
480
+ * @param plaintext - Data to encrypt
481
+ * @param aad - Additional authenticated data (any length, can be empty)
482
+ * @returns Ciphertext + auth tag
483
+ * @throws {ValidationError} On invalid sizes.
484
+ */
485
+ encryptWithAad(key, nonce, plaintext, aad) {
486
+ assertBufferOfSize(key, AES_256_KEY_SIZE, "key");
487
+ assertBufferOfSize(nonce, AES_256_GCM_NONCE_SIZE, "nonce");
488
+ assertBuffer(plaintext, "plaintext");
489
+ assertBuffer(aad, "aad");
490
+ return native.aes256GcmEncryptWithAad(key, nonce, plaintext, aad);
491
+ },
492
+ /**
493
+ * Decrypt with AES-256-GCM and AAD (NEW in v0.2.0).
494
+ *
495
+ * The same AAD used during encryption must be provided. Mismatch = failure.
496
+ *
497
+ * @throws {AuthenticationError} If tag verification fails (incl. AAD mismatch).
498
+ */
499
+ decryptWithAad(key, nonce, ciphertext, aad) {
500
+ assertBufferOfSize(key, AES_256_KEY_SIZE, "key");
501
+ assertBufferOfSize(nonce, AES_256_GCM_NONCE_SIZE, "nonce");
502
+ assertBuffer(ciphertext, "ciphertext");
503
+ assertBuffer(aad, "aad");
504
+ if (ciphertext.length < AES_256_GCM_TAG_SIZE) {
505
+ throw new CryptoError(
506
+ `Ciphertext too short: must be at least ${AES_256_GCM_TAG_SIZE} bytes (tag size)`,
507
+ "aes_gcm_decrypt_with_aad"
508
+ );
509
+ }
510
+ try {
511
+ return native.aes256GcmDecryptWithAad(key, nonce, ciphertext, aad);
512
+ } catch (e) {
513
+ throw new AuthenticationError(
514
+ `AES-256-GCM authentication failed: ${e.message}`
515
+ );
516
+ }
517
+ }
440
518
  });
441
519
  var AES_CBC = Object.freeze({
442
520
  /**
@@ -541,7 +619,220 @@ var SHA256 = Object.freeze({
541
619
  /** Output size in bytes (32). */
542
620
  OUTPUT_SIZE: SHA256_OUTPUT_SIZE
543
621
  });
622
+ var Ed25519 = Object.freeze({
623
+ /**
624
+ * Generate a new random Ed25519 keypair.
625
+ */
626
+ generateKeyPair() {
627
+ const kp = native.ed25519GenerateKeypair();
628
+ return Object.freeze({
629
+ privateKey: kp.private,
630
+ publicKey: kp.public
631
+ });
632
+ },
633
+ /**
634
+ * Derive a deterministic Ed25519 keypair from a 32-byte seed.
635
+ */
636
+ keyPairFromSeed(seed) {
637
+ assertBufferOfSize(seed, ED25519_SEED_SIZE, "seed");
638
+ const kp = native.ed25519KeypairFromSeed(seed);
639
+ return Object.freeze({
640
+ privateKey: kp.private,
641
+ publicKey: kp.public
642
+ });
643
+ },
644
+ /**
645
+ * Derive the public key from a private key.
646
+ */
647
+ publicFromPrivate(privateKey) {
648
+ assertBufferOfSize(privateKey, ED25519_PRIVATE_KEY_SIZE, "privateKey");
649
+ return native.ed25519PublicFromPrivate(privateKey);
650
+ },
651
+ /**
652
+ * Sign a message. Ed25519 signatures are deterministic (RFC 8032).
653
+ */
654
+ sign(privateKey, message) {
655
+ assertBufferOfSize(privateKey, ED25519_PRIVATE_KEY_SIZE, "privateKey");
656
+ assertBuffer(message, "message");
657
+ return native.ed25519Sign(privateKey, message);
658
+ },
659
+ /**
660
+ * Verify a signature. Throws on failure.
661
+ *
662
+ * @throws {SignatureError} If signature is invalid.
663
+ */
664
+ verify(publicKey, message, signature) {
665
+ assertBufferOfSize(publicKey, ED25519_PUBLIC_KEY_SIZE, "publicKey");
666
+ assertBuffer(message, "message");
667
+ assertBufferOfSize(signature, ED25519_SIGNATURE_SIZE, "signature");
668
+ try {
669
+ native.ed25519Verify(publicKey, message, signature);
670
+ } catch (e) {
671
+ throw new SignatureError(e.message);
672
+ }
673
+ },
674
+ /**
675
+ * Verify a signature. Returns boolean (does not throw).
676
+ */
677
+ verifyBool(publicKey, message, signature) {
678
+ if (!Buffer.isBuffer(publicKey) || publicKey.length !== ED25519_PUBLIC_KEY_SIZE) return false;
679
+ if (!Buffer.isBuffer(message)) return false;
680
+ if (!Buffer.isBuffer(signature) || signature.length !== ED25519_SIGNATURE_SIZE) return false;
681
+ return native.ed25519VerifyBool(publicKey, message, signature);
682
+ },
683
+ /** Private key size in bytes (32). */
684
+ PRIVATE_KEY_SIZE: ED25519_PRIVATE_KEY_SIZE,
685
+ /** Public key size in bytes (32). */
686
+ PUBLIC_KEY_SIZE: ED25519_PUBLIC_KEY_SIZE,
687
+ /** Signature size in bytes (64). */
688
+ SIGNATURE_SIZE: ED25519_SIGNATURE_SIZE,
689
+ /** Seed size for deterministic keypair derivation (32). */
690
+ SEED_SIZE: ED25519_SEED_SIZE
691
+ });
692
+ var XEd25519 = Object.freeze({
693
+ /**
694
+ * Sign a message using a Curve25519 private key. Uses OS RNG.
695
+ * Signatures are NOT deterministic (different each call).
696
+ */
697
+ sign(privateKey, message) {
698
+ assertBufferOfSize(privateKey, XED25519_PRIVATE_KEY_SIZE, "privateKey");
699
+ assertBuffer(message, "message");
700
+ return native.xed25519Sign(privateKey, message);
701
+ },
702
+ /**
703
+ * Sign with explicit 64-byte random nonce (for testing/reproducibility).
704
+ */
705
+ signWithRandom(privateKey, message, random) {
706
+ assertBufferOfSize(privateKey, XED25519_PRIVATE_KEY_SIZE, "privateKey");
707
+ assertBuffer(message, "message");
708
+ assertBufferOfSize(random, XED25519_RANDOM_SIZE, "random");
709
+ return native.xed25519SignWithRandom(privateKey, message, random);
710
+ },
711
+ /**
712
+ * Verify a XEd25519 signature. Throws on failure.
713
+ *
714
+ * @throws {SignatureError} If signature is invalid.
715
+ */
716
+ verify(publicKey, message, signature) {
717
+ assertBufferOfSize(publicKey, XED25519_PUBLIC_KEY_SIZE, "publicKey");
718
+ assertBuffer(message, "message");
719
+ assertBufferOfSize(signature, XED25519_SIGNATURE_SIZE, "signature");
720
+ try {
721
+ native.xed25519Verify(publicKey, message, signature);
722
+ } catch (e) {
723
+ throw new SignatureError(e.message);
724
+ }
725
+ },
726
+ /**
727
+ * Verify a XEd25519 signature. Returns boolean (does not throw).
728
+ */
729
+ verifyBool(publicKey, message, signature) {
730
+ if (!Buffer.isBuffer(publicKey) || publicKey.length !== XED25519_PUBLIC_KEY_SIZE) return false;
731
+ if (!Buffer.isBuffer(message)) return false;
732
+ if (!Buffer.isBuffer(signature) || signature.length !== XED25519_SIGNATURE_SIZE) return false;
733
+ return native.xed25519VerifyBool(publicKey, message, signature);
734
+ },
735
+ /** Private key size in bytes (32, same as Curve25519). */
736
+ PRIVATE_KEY_SIZE: XED25519_PRIVATE_KEY_SIZE,
737
+ /** Public key size in bytes (32, same as Curve25519). */
738
+ PUBLIC_KEY_SIZE: XED25519_PUBLIC_KEY_SIZE,
739
+ /** Signature size in bytes (64). */
740
+ SIGNATURE_SIZE: XED25519_SIGNATURE_SIZE,
741
+ /** Random nonce size for signing (64). */
742
+ RANDOM_SIZE: XED25519_RANDOM_SIZE
743
+ });
544
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
+ }
545
836
 
546
837
  // src/types.ts
547
838
  function asPublicKey(buf) {
@@ -553,10 +844,13 @@ function asPrivateKey(buf) {
553
844
  function asSharedSecret(buf) {
554
845
  return buf;
555
846
  }
847
+ function asSignature(buf) {
848
+ return buf;
849
+ }
556
850
 
557
851
  // src/utils.ts
558
852
  var import_node_crypto = require("crypto");
559
- function secureRandom(length) {
853
+ function secureRandom2(length) {
560
854
  if (!Number.isInteger(length) || length < 0) {
561
855
  throw new RangeError(`length must be a non-negative integer, got ${length}`);
562
856
  }
@@ -626,17 +920,19 @@ function xor(a, b) {
626
920
  }
627
921
 
628
922
  // src/index.ts
629
- var VERSION = "0.1.0";
923
+ var VERSION = "0.2.0";
630
924
  var SignalisCore = Object.freeze({
631
925
  // Crypto primitives
632
926
  Curve25519,
927
+ Ed25519,
928
+ XEd25519,
633
929
  HKDF,
634
930
  AES_GCM,
635
931
  AES_CBC,
636
932
  HMAC,
637
933
  SHA256,
638
934
  // Random
639
- secureRandom,
935
+ secureRandom: secureRandom2,
640
936
  randomNonce,
641
937
  randomIv,
642
938
  randomKey,
@@ -648,7 +944,7 @@ var SignalisCore = Object.freeze({
648
944
  // Security
649
945
  constantTimeEqual,
650
946
  // Version
651
- VERSION: "0.1.0",
947
+ VERSION: "0.2.0",
652
948
  nativeVersion
653
949
  });
654
950
  var index_default = SignalisCore;
@@ -664,11 +960,20 @@ var index_default = SignalisCore;
664
960
  AES_GCM_MAX_PLAINTEXT_SIZE,
665
961
  AES_GCM_RECOMMENDED_MESSAGES_PER_KEY,
666
962
  AuthenticationError,
963
+ CHACHA20_POLY1305_KEY_SIZE,
964
+ CHACHA20_POLY1305_NONCE_SIZE,
965
+ CHACHA20_POLY1305_TAG_SIZE,
667
966
  CURVE25519_PRIVATE_KEY_SIZE,
668
967
  CURVE25519_PUBLIC_KEY_SIZE,
669
968
  CURVE25519_SHARED_SECRET_SIZE,
969
+ ChaCha20Poly1305,
670
970
  CryptoError,
671
971
  Curve25519,
972
+ ED25519_PRIVATE_KEY_SIZE,
973
+ ED25519_PUBLIC_KEY_SIZE,
974
+ ED25519_SEED_SIZE,
975
+ ED25519_SIGNATURE_SIZE,
976
+ Ed25519,
672
977
  HKDF,
673
978
  HKDF_MAX_OUTPUT_SIZE,
674
979
  HKDF_PRK_SIZE,
@@ -680,11 +985,18 @@ var index_default = SignalisCore;
680
985
  SHA256_BLOCK_SIZE,
681
986
  SHA256_OUTPUT_SIZE,
682
987
  SignalisError,
988
+ SignatureError,
683
989
  VERSION,
684
990
  ValidationError,
991
+ XED25519_PRIVATE_KEY_SIZE,
992
+ XED25519_PUBLIC_KEY_SIZE,
993
+ XED25519_RANDOM_SIZE,
994
+ XED25519_SIGNATURE_SIZE,
995
+ XEd25519,
685
996
  asPrivateKey,
686
997
  asPublicKey,
687
998
  asSharedSecret,
999
+ asSignature,
688
1000
  assertBuffer,
689
1001
  assertBufferLength,
690
1002
  assertBufferOfSize,
@@ -693,10 +1005,12 @@ var index_default = SignalisCore;
693
1005
  bufferToString,
694
1006
  buffersSameLength,
695
1007
  concat,
1008
+ constantTimeEq,
696
1009
  constantTimeEqual,
697
1010
  fromBase64,
698
1011
  fromBase64Url,
699
1012
  fromHex,
1013
+ nativeSecureRandom,
700
1014
  nativeVersion,
701
1015
  randomIv,
702
1016
  randomKey,