@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/dist/index.mjs CHANGED
@@ -5,6 +5,14 @@ import * as native from "../index.js";
5
5
  var CURVE25519_PRIVATE_KEY_SIZE = 32;
6
6
  var CURVE25519_PUBLIC_KEY_SIZE = 32;
7
7
  var CURVE25519_SHARED_SECRET_SIZE = 32;
8
+ var ED25519_PRIVATE_KEY_SIZE = 32;
9
+ var ED25519_PUBLIC_KEY_SIZE = 32;
10
+ var ED25519_SIGNATURE_SIZE = 64;
11
+ var ED25519_SEED_SIZE = 32;
12
+ var XED25519_PRIVATE_KEY_SIZE = 32;
13
+ var XED25519_PUBLIC_KEY_SIZE = 32;
14
+ var XED25519_SIGNATURE_SIZE = 64;
15
+ var XED25519_RANDOM_SIZE = 64;
8
16
  var HKDF_PRK_SIZE = 32;
9
17
  var HKDF_MAX_OUTPUT_SIZE = 8160;
10
18
  var AES_256_KEY_SIZE = 32;
@@ -67,6 +75,12 @@ var KeyDerivationError = class extends CryptoError {
67
75
  this.name = "KeyDerivationError";
68
76
  }
69
77
  };
78
+ var SignatureError = class extends CryptoError {
79
+ constructor(message = "Signature verification failed") {
80
+ super(message, "verify_signature");
81
+ this.name = "SignatureError";
82
+ }
83
+ };
70
84
  var LengthError = class extends ValidationError {
71
85
  constructor(message, options = {}) {
72
86
  super(message, options);
@@ -346,7 +360,53 @@ var AES_GCM = Object.freeze({
346
360
  /** Nonce size in bytes (12). */
347
361
  NONCE_SIZE: AES_256_GCM_NONCE_SIZE,
348
362
  /** Tag size in bytes (16). */
349
- TAG_SIZE: AES_256_GCM_TAG_SIZE
363
+ TAG_SIZE: AES_256_GCM_TAG_SIZE,
364
+ /**
365
+ * Encrypt with AES-256-GCM and Additional Authenticated Data (NEW in v0.2.0).
366
+ *
367
+ * AAD is authenticated but NOT encrypted. Useful for binding metadata
368
+ * (like message headers) to the ciphertext.
369
+ *
370
+ * @param key - 32-byte symmetric key
371
+ * @param nonce - 12-byte unique nonce
372
+ * @param plaintext - Data to encrypt
373
+ * @param aad - Additional authenticated data (any length, can be empty)
374
+ * @returns Ciphertext + auth tag
375
+ * @throws {ValidationError} On invalid sizes.
376
+ */
377
+ encryptWithAad(key, nonce, plaintext, aad) {
378
+ assertBufferOfSize(key, AES_256_KEY_SIZE, "key");
379
+ assertBufferOfSize(nonce, AES_256_GCM_NONCE_SIZE, "nonce");
380
+ assertBuffer(plaintext, "plaintext");
381
+ assertBuffer(aad, "aad");
382
+ return native.aes256GcmEncryptWithAad(key, nonce, plaintext, aad);
383
+ },
384
+ /**
385
+ * Decrypt with AES-256-GCM and AAD (NEW in v0.2.0).
386
+ *
387
+ * The same AAD used during encryption must be provided. Mismatch = failure.
388
+ *
389
+ * @throws {AuthenticationError} If tag verification fails (incl. AAD mismatch).
390
+ */
391
+ decryptWithAad(key, nonce, ciphertext, aad) {
392
+ assertBufferOfSize(key, AES_256_KEY_SIZE, "key");
393
+ assertBufferOfSize(nonce, AES_256_GCM_NONCE_SIZE, "nonce");
394
+ assertBuffer(ciphertext, "ciphertext");
395
+ assertBuffer(aad, "aad");
396
+ if (ciphertext.length < AES_256_GCM_TAG_SIZE) {
397
+ throw new CryptoError(
398
+ `Ciphertext too short: must be at least ${AES_256_GCM_TAG_SIZE} bytes (tag size)`,
399
+ "aes_gcm_decrypt_with_aad"
400
+ );
401
+ }
402
+ try {
403
+ return native.aes256GcmDecryptWithAad(key, nonce, ciphertext, aad);
404
+ } catch (e) {
405
+ throw new AuthenticationError(
406
+ `AES-256-GCM authentication failed: ${e.message}`
407
+ );
408
+ }
409
+ }
350
410
  });
351
411
  var AES_CBC = Object.freeze({
352
412
  /**
@@ -451,7 +511,220 @@ var SHA256 = Object.freeze({
451
511
  /** Output size in bytes (32). */
452
512
  OUTPUT_SIZE: SHA256_OUTPUT_SIZE
453
513
  });
514
+ var Ed25519 = Object.freeze({
515
+ /**
516
+ * Generate a new random Ed25519 keypair.
517
+ */
518
+ generateKeyPair() {
519
+ const kp = native.ed25519GenerateKeypair();
520
+ return Object.freeze({
521
+ privateKey: kp.private,
522
+ publicKey: kp.public
523
+ });
524
+ },
525
+ /**
526
+ * Derive a deterministic Ed25519 keypair from a 32-byte seed.
527
+ */
528
+ keyPairFromSeed(seed) {
529
+ assertBufferOfSize(seed, ED25519_SEED_SIZE, "seed");
530
+ const kp = native.ed25519KeypairFromSeed(seed);
531
+ return Object.freeze({
532
+ privateKey: kp.private,
533
+ publicKey: kp.public
534
+ });
535
+ },
536
+ /**
537
+ * Derive the public key from a private key.
538
+ */
539
+ publicFromPrivate(privateKey) {
540
+ assertBufferOfSize(privateKey, ED25519_PRIVATE_KEY_SIZE, "privateKey");
541
+ return native.ed25519PublicFromPrivate(privateKey);
542
+ },
543
+ /**
544
+ * Sign a message. Ed25519 signatures are deterministic (RFC 8032).
545
+ */
546
+ sign(privateKey, message) {
547
+ assertBufferOfSize(privateKey, ED25519_PRIVATE_KEY_SIZE, "privateKey");
548
+ assertBuffer(message, "message");
549
+ return native.ed25519Sign(privateKey, message);
550
+ },
551
+ /**
552
+ * Verify a signature. Throws on failure.
553
+ *
554
+ * @throws {SignatureError} If signature is invalid.
555
+ */
556
+ verify(publicKey, message, signature) {
557
+ assertBufferOfSize(publicKey, ED25519_PUBLIC_KEY_SIZE, "publicKey");
558
+ assertBuffer(message, "message");
559
+ assertBufferOfSize(signature, ED25519_SIGNATURE_SIZE, "signature");
560
+ try {
561
+ native.ed25519Verify(publicKey, message, signature);
562
+ } catch (e) {
563
+ throw new SignatureError(e.message);
564
+ }
565
+ },
566
+ /**
567
+ * Verify a signature. Returns boolean (does not throw).
568
+ */
569
+ verifyBool(publicKey, message, signature) {
570
+ if (!Buffer.isBuffer(publicKey) || publicKey.length !== ED25519_PUBLIC_KEY_SIZE) return false;
571
+ if (!Buffer.isBuffer(message)) return false;
572
+ if (!Buffer.isBuffer(signature) || signature.length !== ED25519_SIGNATURE_SIZE) return false;
573
+ return native.ed25519VerifyBool(publicKey, message, signature);
574
+ },
575
+ /** Private key size in bytes (32). */
576
+ PRIVATE_KEY_SIZE: ED25519_PRIVATE_KEY_SIZE,
577
+ /** Public key size in bytes (32). */
578
+ PUBLIC_KEY_SIZE: ED25519_PUBLIC_KEY_SIZE,
579
+ /** Signature size in bytes (64). */
580
+ SIGNATURE_SIZE: ED25519_SIGNATURE_SIZE,
581
+ /** Seed size for deterministic keypair derivation (32). */
582
+ SEED_SIZE: ED25519_SEED_SIZE
583
+ });
584
+ var XEd25519 = Object.freeze({
585
+ /**
586
+ * Sign a message using a Curve25519 private key. Uses OS RNG.
587
+ * Signatures are NOT deterministic (different each call).
588
+ */
589
+ sign(privateKey, message) {
590
+ assertBufferOfSize(privateKey, XED25519_PRIVATE_KEY_SIZE, "privateKey");
591
+ assertBuffer(message, "message");
592
+ return native.xed25519Sign(privateKey, message);
593
+ },
594
+ /**
595
+ * Sign with explicit 64-byte random nonce (for testing/reproducibility).
596
+ */
597
+ signWithRandom(privateKey, message, random) {
598
+ assertBufferOfSize(privateKey, XED25519_PRIVATE_KEY_SIZE, "privateKey");
599
+ assertBuffer(message, "message");
600
+ assertBufferOfSize(random, XED25519_RANDOM_SIZE, "random");
601
+ return native.xed25519SignWithRandom(privateKey, message, random);
602
+ },
603
+ /**
604
+ * Verify a XEd25519 signature. Throws on failure.
605
+ *
606
+ * @throws {SignatureError} If signature is invalid.
607
+ */
608
+ verify(publicKey, message, signature) {
609
+ assertBufferOfSize(publicKey, XED25519_PUBLIC_KEY_SIZE, "publicKey");
610
+ assertBuffer(message, "message");
611
+ assertBufferOfSize(signature, XED25519_SIGNATURE_SIZE, "signature");
612
+ try {
613
+ native.xed25519Verify(publicKey, message, signature);
614
+ } catch (e) {
615
+ throw new SignatureError(e.message);
616
+ }
617
+ },
618
+ /**
619
+ * Verify a XEd25519 signature. Returns boolean (does not throw).
620
+ */
621
+ verifyBool(publicKey, message, signature) {
622
+ if (!Buffer.isBuffer(publicKey) || publicKey.length !== XED25519_PUBLIC_KEY_SIZE) return false;
623
+ if (!Buffer.isBuffer(message)) return false;
624
+ if (!Buffer.isBuffer(signature) || signature.length !== XED25519_SIGNATURE_SIZE) return false;
625
+ return native.xed25519VerifyBool(publicKey, message, signature);
626
+ },
627
+ /** Private key size in bytes (32, same as Curve25519). */
628
+ PRIVATE_KEY_SIZE: XED25519_PRIVATE_KEY_SIZE,
629
+ /** Public key size in bytes (32, same as Curve25519). */
630
+ PUBLIC_KEY_SIZE: XED25519_PUBLIC_KEY_SIZE,
631
+ /** Signature size in bytes (64). */
632
+ SIGNATURE_SIZE: XED25519_SIGNATURE_SIZE,
633
+ /** Random nonce size for signing (64). */
634
+ RANDOM_SIZE: XED25519_RANDOM_SIZE
635
+ });
454
636
  var nativeVersion = native.version();
637
+ var CHACHA20_POLY1305_KEY_SIZE = 32;
638
+ var CHACHA20_POLY1305_NONCE_SIZE = 12;
639
+ var CHACHA20_POLY1305_TAG_SIZE = 16;
640
+ var ChaCha20Poly1305 = Object.freeze({
641
+ /**
642
+ * Encrypt + authenticate `plaintext`.
643
+ *
644
+ * @param key 32-byte key
645
+ * @param nonce 12-byte nonce — MUST be unique per (key, plaintext)
646
+ * @param plaintext data to encrypt
647
+ * @returns ciphertext || tag (16 bytes appended)
648
+ */
649
+ encrypt(key, nonce, plaintext) {
650
+ if (!Buffer.isBuffer(key) || key.length !== CHACHA20_POLY1305_KEY_SIZE) {
651
+ throw new RangeError(`key must be ${CHACHA20_POLY1305_KEY_SIZE} bytes`);
652
+ }
653
+ if (!Buffer.isBuffer(nonce) || nonce.length !== CHACHA20_POLY1305_NONCE_SIZE) {
654
+ throw new RangeError(`nonce must be ${CHACHA20_POLY1305_NONCE_SIZE} bytes`);
655
+ }
656
+ if (!Buffer.isBuffer(plaintext)) {
657
+ throw new TypeError("plaintext must be a Buffer");
658
+ }
659
+ return native.chacha20Poly1305Encrypt(key, nonce, plaintext);
660
+ },
661
+ /**
662
+ * Verify-then-decrypt. Returns plaintext on success, throws on auth failure.
663
+ */
664
+ decrypt(key, nonce, ciphertext) {
665
+ if (!Buffer.isBuffer(key) || key.length !== CHACHA20_POLY1305_KEY_SIZE) {
666
+ throw new RangeError(`key must be ${CHACHA20_POLY1305_KEY_SIZE} bytes`);
667
+ }
668
+ if (!Buffer.isBuffer(nonce) || nonce.length !== CHACHA20_POLY1305_NONCE_SIZE) {
669
+ throw new RangeError(`nonce must be ${CHACHA20_POLY1305_NONCE_SIZE} bytes`);
670
+ }
671
+ if (!Buffer.isBuffer(ciphertext)) {
672
+ throw new TypeError("ciphertext must be a Buffer");
673
+ }
674
+ return native.chacha20Poly1305Decrypt(key, nonce, ciphertext);
675
+ },
676
+ /**
677
+ * Encrypt + authenticate with Additional Authenticated Data.
678
+ *
679
+ * AAD is NOT encrypted but IS authenticated. Use for plaintext metadata
680
+ * (e.g., message headers) that must not be tampered with.
681
+ */
682
+ encryptWithAad(key, nonce, plaintext, aad) {
683
+ if (!Buffer.isBuffer(key) || key.length !== CHACHA20_POLY1305_KEY_SIZE) {
684
+ throw new RangeError(`key must be ${CHACHA20_POLY1305_KEY_SIZE} bytes`);
685
+ }
686
+ if (!Buffer.isBuffer(nonce) || nonce.length !== CHACHA20_POLY1305_NONCE_SIZE) {
687
+ throw new RangeError(`nonce must be ${CHACHA20_POLY1305_NONCE_SIZE} bytes`);
688
+ }
689
+ if (!Buffer.isBuffer(plaintext) || !Buffer.isBuffer(aad)) {
690
+ throw new TypeError("plaintext and aad must be Buffers");
691
+ }
692
+ return native.chacha20Poly1305EncryptWithAad(key, nonce, plaintext, aad);
693
+ },
694
+ /**
695
+ * Verify (key + nonce + ciphertext + AAD) and decrypt.
696
+ */
697
+ decryptWithAad(key, nonce, ciphertext, aad) {
698
+ if (!Buffer.isBuffer(key) || key.length !== CHACHA20_POLY1305_KEY_SIZE) {
699
+ throw new RangeError(`key must be ${CHACHA20_POLY1305_KEY_SIZE} bytes`);
700
+ }
701
+ if (!Buffer.isBuffer(nonce) || nonce.length !== CHACHA20_POLY1305_NONCE_SIZE) {
702
+ throw new RangeError(`nonce must be ${CHACHA20_POLY1305_NONCE_SIZE} bytes`);
703
+ }
704
+ if (!Buffer.isBuffer(ciphertext) || !Buffer.isBuffer(aad)) {
705
+ throw new TypeError("ciphertext and aad must be Buffers");
706
+ }
707
+ return native.chacha20Poly1305DecryptWithAad(key, nonce, ciphertext, aad);
708
+ },
709
+ /** Key size in bytes (32). */
710
+ KEY_SIZE: CHACHA20_POLY1305_KEY_SIZE,
711
+ /** Nonce size in bytes (12). */
712
+ NONCE_SIZE: CHACHA20_POLY1305_NONCE_SIZE,
713
+ /** Authentication tag size in bytes (16), appended to ciphertext. */
714
+ TAG_SIZE: CHACHA20_POLY1305_TAG_SIZE
715
+ });
716
+ function constantTimeEq2(a, b) {
717
+ if (!Buffer.isBuffer(a) || !Buffer.isBuffer(b)) {
718
+ throw new TypeError("constantTimeEq: both arguments must be Buffers");
719
+ }
720
+ return native.constantTimeEq(a, b);
721
+ }
722
+ function nativeSecureRandom(size) {
723
+ if (!Number.isInteger(size) || size <= 0) {
724
+ throw new RangeError(`size must be a positive integer, got ${size}`);
725
+ }
726
+ return native.secureRandom(size);
727
+ }
455
728
 
456
729
  // src/types.ts
457
730
  function asPublicKey(buf) {
@@ -463,10 +736,13 @@ function asPrivateKey(buf) {
463
736
  function asSharedSecret(buf) {
464
737
  return buf;
465
738
  }
739
+ function asSignature(buf) {
740
+ return buf;
741
+ }
466
742
 
467
743
  // src/utils.ts
468
744
  import { randomBytes, timingSafeEqual } from "crypto";
469
- function secureRandom(length) {
745
+ function secureRandom2(length) {
470
746
  if (!Number.isInteger(length) || length < 0) {
471
747
  throw new RangeError(`length must be a non-negative integer, got ${length}`);
472
748
  }
@@ -536,17 +812,19 @@ function xor(a, b) {
536
812
  }
537
813
 
538
814
  // src/index.ts
539
- var VERSION = "0.1.0";
815
+ var VERSION = "0.2.0";
540
816
  var SignalisCore = Object.freeze({
541
817
  // Crypto primitives
542
818
  Curve25519,
819
+ Ed25519,
820
+ XEd25519,
543
821
  HKDF,
544
822
  AES_GCM,
545
823
  AES_CBC,
546
824
  HMAC,
547
825
  SHA256,
548
826
  // Random
549
- secureRandom,
827
+ secureRandom: secureRandom2,
550
828
  randomNonce,
551
829
  randomIv,
552
830
  randomKey,
@@ -558,7 +836,7 @@ var SignalisCore = Object.freeze({
558
836
  // Security
559
837
  constantTimeEqual,
560
838
  // Version
561
- VERSION: "0.1.0",
839
+ VERSION: "0.2.0",
562
840
  nativeVersion
563
841
  });
564
842
  var index_default = SignalisCore;
@@ -573,11 +851,20 @@ export {
573
851
  AES_GCM_MAX_PLAINTEXT_SIZE,
574
852
  AES_GCM_RECOMMENDED_MESSAGES_PER_KEY,
575
853
  AuthenticationError,
854
+ CHACHA20_POLY1305_KEY_SIZE,
855
+ CHACHA20_POLY1305_NONCE_SIZE,
856
+ CHACHA20_POLY1305_TAG_SIZE,
576
857
  CURVE25519_PRIVATE_KEY_SIZE,
577
858
  CURVE25519_PUBLIC_KEY_SIZE,
578
859
  CURVE25519_SHARED_SECRET_SIZE,
860
+ ChaCha20Poly1305,
579
861
  CryptoError,
580
862
  Curve25519,
863
+ ED25519_PRIVATE_KEY_SIZE,
864
+ ED25519_PUBLIC_KEY_SIZE,
865
+ ED25519_SEED_SIZE,
866
+ ED25519_SIGNATURE_SIZE,
867
+ Ed25519,
581
868
  HKDF,
582
869
  HKDF_MAX_OUTPUT_SIZE,
583
870
  HKDF_PRK_SIZE,
@@ -589,11 +876,18 @@ export {
589
876
  SHA256_BLOCK_SIZE,
590
877
  SHA256_OUTPUT_SIZE,
591
878
  SignalisError,
879
+ SignatureError,
592
880
  VERSION,
593
881
  ValidationError,
882
+ XED25519_PRIVATE_KEY_SIZE,
883
+ XED25519_PUBLIC_KEY_SIZE,
884
+ XED25519_RANDOM_SIZE,
885
+ XED25519_SIGNATURE_SIZE,
886
+ XEd25519,
594
887
  asPrivateKey,
595
888
  asPublicKey,
596
889
  asSharedSecret,
890
+ asSignature,
597
891
  assertBuffer,
598
892
  assertBufferLength,
599
893
  assertBufferOfSize,
@@ -602,16 +896,18 @@ export {
602
896
  bufferToString,
603
897
  buffersSameLength,
604
898
  concat,
899
+ constantTimeEq2 as constantTimeEq,
605
900
  constantTimeEqual,
606
901
  index_default as default,
607
902
  fromBase64,
608
903
  fromBase64Url,
609
904
  fromHex,
905
+ nativeSecureRandom,
610
906
  nativeVersion,
611
907
  randomIv,
612
908
  randomKey,
613
909
  randomNonce,
614
- secureRandom,
910
+ secureRandom2 as secureRandom,
615
911
  stringToBuffer,
616
912
  toBase64,
617
913
  toBase64Url,