@reley/crypto 0.1.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.
Files changed (47) hide show
  1. package/dist/cipher.d.ts +16 -0
  2. package/dist/cipher.d.ts.map +1 -0
  3. package/dist/cipher.js +44 -0
  4. package/dist/cipher.js.map +1 -0
  5. package/dist/ecdh.d.ts +6 -0
  6. package/dist/ecdh.d.ts.map +1 -0
  7. package/dist/ecdh.js +10 -0
  8. package/dist/ecdh.js.map +1 -0
  9. package/dist/hkdf.d.ts +16 -0
  10. package/dist/hkdf.d.ts.map +1 -0
  11. package/dist/hkdf.js +54 -0
  12. package/dist/hkdf.js.map +1 -0
  13. package/dist/index.d.ts +8 -0
  14. package/dist/index.d.ts.map +1 -0
  15. package/dist/index.js +8 -0
  16. package/dist/index.js.map +1 -0
  17. package/dist/keys.d.ts +52 -0
  18. package/dist/keys.d.ts.map +1 -0
  19. package/dist/keys.js +80 -0
  20. package/dist/keys.js.map +1 -0
  21. package/dist/qr-payload.d.ts +19 -0
  22. package/dist/qr-payload.d.ts.map +1 -0
  23. package/dist/qr-payload.js +84 -0
  24. package/dist/qr-payload.js.map +1 -0
  25. package/dist/ratchet.d.ts +34 -0
  26. package/dist/ratchet.d.ts.map +1 -0
  27. package/dist/ratchet.js +91 -0
  28. package/dist/ratchet.js.map +1 -0
  29. package/dist/utils.d.ts +15 -0
  30. package/dist/utils.d.ts.map +1 -0
  31. package/dist/utils.js +37 -0
  32. package/dist/utils.js.map +1 -0
  33. package/package.json +32 -0
  34. package/src/__tests__/cipher.test.ts +57 -0
  35. package/src/__tests__/ecdh.test.ts +27 -0
  36. package/src/__tests__/hkdf.test.ts +62 -0
  37. package/src/__tests__/keys.test.ts +62 -0
  38. package/src/__tests__/qr-payload.test.ts +62 -0
  39. package/src/__tests__/ratchet.test.ts +119 -0
  40. package/src/cipher.ts +90 -0
  41. package/src/ecdh.ts +13 -0
  42. package/src/hkdf.ts +69 -0
  43. package/src/index.ts +46 -0
  44. package/src/keys.ts +105 -0
  45. package/src/qr-payload.ts +112 -0
  46. package/src/ratchet.ts +124 -0
  47. package/src/utils.ts +41 -0
@@ -0,0 +1,16 @@
1
+ export declare const NONCE_LENGTH = 12;
2
+ export declare const TAG_LENGTH = 16;
3
+ export declare const KEY_LENGTH = 32;
4
+ /**
5
+ * Encrypt data with XChaCha20-Poly1305.
6
+ * @returns { nonce, ciphertext } where ciphertext includes the auth tag
7
+ */
8
+ export declare function encrypt(plaintext: Uint8Array, key: Uint8Array, aad?: Uint8Array): Promise<{
9
+ nonce: Uint8Array;
10
+ ciphertext: Uint8Array;
11
+ }>;
12
+ /**
13
+ * Decrypt data with XChaCha20-Poly1305.
14
+ */
15
+ export declare function decrypt(ciphertext: Uint8Array, nonce: Uint8Array, key: Uint8Array, aad?: Uint8Array): Promise<Uint8Array>;
16
+ //# sourceMappingURL=cipher.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cipher.d.ts","sourceRoot":"","sources":["../src/cipher.ts"],"names":[],"mappings":"AAsBA,eAAO,MAAM,YAAY,KAAK,CAAC;AAC/B,eAAO,MAAM,UAAU,KAAK,CAAC;AAC7B,eAAO,MAAM,UAAU,KAAK,CAAC;AAE7B;;;GAGG;AACH,wBAAsB,OAAO,CAC3B,SAAS,EAAE,UAAU,EACrB,GAAG,EAAE,UAAU,EACf,GAAG,GAAE,UAA8B,GAClC,OAAO,CAAC;IAAE,KAAK,EAAE,UAAU,CAAC;IAAC,UAAU,EAAE,UAAU,CAAA;CAAE,CAAC,CAkBxD;AAED;;GAEG;AACH,wBAAsB,OAAO,CAC3B,UAAU,EAAE,UAAU,EACtB,KAAK,EAAE,UAAU,EACjB,GAAG,EAAE,UAAU,EACf,GAAG,GAAE,UAA8B,GAClC,OAAO,CAAC,UAAU,CAAC,CAkBrB"}
package/dist/cipher.js ADDED
@@ -0,0 +1,44 @@
1
+ import { ensureSodium } from './keys.js';
2
+ export const NONCE_LENGTH = 12;
3
+ export const TAG_LENGTH = 16;
4
+ export const KEY_LENGTH = 32;
5
+ /**
6
+ * Encrypt data with XChaCha20-Poly1305.
7
+ * @returns { nonce, ciphertext } where ciphertext includes the auth tag
8
+ */
9
+ export async function encrypt(plaintext, key, aad = new Uint8Array(0)) {
10
+ const s = await ensureSodium();
11
+ if (key.length !== KEY_LENGTH) {
12
+ throw new Error(`Key must be ${KEY_LENGTH} bytes, got ${key.length}`);
13
+ }
14
+ const nonce = s.randombytes_buf(NONCE_LENGTH);
15
+ const ciphertext = s.crypto_aead_xchacha20poly1305_ietf_encrypt(plaintext, aad.length > 0 ? aad : null, null, // nsec (unused)
16
+ // xchacha uses 24-byte nonce, pad our 12-byte nonce
17
+ padNonce(nonce, s), key);
18
+ return { nonce, ciphertext };
19
+ }
20
+ /**
21
+ * Decrypt data with XChaCha20-Poly1305.
22
+ */
23
+ export async function decrypt(ciphertext, nonce, key, aad = new Uint8Array(0)) {
24
+ const s = await ensureSodium();
25
+ if (key.length !== KEY_LENGTH) {
26
+ throw new Error(`Key must be ${KEY_LENGTH} bytes, got ${key.length}`);
27
+ }
28
+ try {
29
+ return s.crypto_aead_xchacha20poly1305_ietf_decrypt(null, // nsec (unused)
30
+ ciphertext, aad.length > 0 ? aad : null, padNonce(nonce, s), key);
31
+ }
32
+ catch {
33
+ throw new Error('Decryption failed: invalid ciphertext or key');
34
+ }
35
+ }
36
+ /**
37
+ * Pad a 12-byte nonce to 24 bytes for xchacha20poly1305.
38
+ */
39
+ function padNonce(nonce, s) {
40
+ const padded = new Uint8Array(s.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES);
41
+ padded.set(nonce, 0);
42
+ return padded;
43
+ }
44
+ //# sourceMappingURL=cipher.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cipher.js","sourceRoot":"","sources":["../src/cipher.ts"],"names":[],"mappings":"AAoBA,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAEzC,MAAM,CAAC,MAAM,YAAY,GAAG,EAAE,CAAC;AAC/B,MAAM,CAAC,MAAM,UAAU,GAAG,EAAE,CAAC;AAC7B,MAAM,CAAC,MAAM,UAAU,GAAG,EAAE,CAAC;AAE7B;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,OAAO,CAC3B,SAAqB,EACrB,GAAe,EACf,MAAkB,IAAI,UAAU,CAAC,CAAC,CAAC;IAEnC,MAAM,CAAC,GAAG,MAAM,YAAY,EAAE,CAAC;IAE/B,IAAI,GAAG,CAAC,MAAM,KAAK,UAAU,EAAE,CAAC;QAC9B,MAAM,IAAI,KAAK,CAAC,eAAe,UAAU,eAAe,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC;IACxE,CAAC;IAED,MAAM,KAAK,GAAG,CAAC,CAAC,eAAe,CAAC,YAAY,CAAC,CAAC;IAC9C,MAAM,UAAU,GAAG,CAAC,CAAC,0CAA0C,CAC7D,SAAS,EACT,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAC3B,IAAI,EAAE,gBAAgB;IACtB,oDAAoD;IACpD,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC,EAClB,GAAG,CACJ,CAAC;IAEF,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,CAAC;AAC/B,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,OAAO,CAC3B,UAAsB,EACtB,KAAiB,EACjB,GAAe,EACf,MAAkB,IAAI,UAAU,CAAC,CAAC,CAAC;IAEnC,MAAM,CAAC,GAAG,MAAM,YAAY,EAAE,CAAC;IAE/B,IAAI,GAAG,CAAC,MAAM,KAAK,UAAU,EAAE,CAAC;QAC9B,MAAM,IAAI,KAAK,CAAC,eAAe,UAAU,eAAe,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC;IACxE,CAAC;IAED,IAAI,CAAC;QACH,OAAO,CAAC,CAAC,0CAA0C,CACjD,IAAI,EAAE,gBAAgB;QACtB,UAAU,EACV,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAC3B,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC,EAClB,GAAG,CACJ,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,KAAK,CAAC,8CAA8C,CAAC,CAAC;IAClE,CAAC;AACH,CAAC;AAED;;GAEG;AACH,SAAS,QAAQ,CAAC,KAAiB,EAAE,CAAiB;IACpD,MAAM,MAAM,GAAG,IAAI,UAAU,CAAC,CAAC,CAAC,4CAA4C,CAAC,CAAC;IAC9E,MAAM,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IACrB,OAAO,MAAM,CAAC;AAChB,CAAC"}
package/dist/ecdh.d.ts ADDED
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Compute X25519 ECDH shared secret from our secret key and their public key.
3
+ * Returns 32-byte raw shared secret.
4
+ */
5
+ export declare function computeSharedSecret(ourSecretKey: Uint8Array, theirPublicKey: Uint8Array): Promise<Uint8Array>;
6
+ //# sourceMappingURL=ecdh.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ecdh.d.ts","sourceRoot":"","sources":["../src/ecdh.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,wBAAsB,mBAAmB,CACvC,YAAY,EAAE,UAAU,EACxB,cAAc,EAAE,UAAU,GACzB,OAAO,CAAC,UAAU,CAAC,CAGrB"}
package/dist/ecdh.js ADDED
@@ -0,0 +1,10 @@
1
+ import { ensureSodium } from './keys.js';
2
+ /**
3
+ * Compute X25519 ECDH shared secret from our secret key and their public key.
4
+ * Returns 32-byte raw shared secret.
5
+ */
6
+ export async function computeSharedSecret(ourSecretKey, theirPublicKey) {
7
+ const s = await ensureSodium();
8
+ return s.crypto_scalarmult(ourSecretKey, theirPublicKey);
9
+ }
10
+ //# sourceMappingURL=ecdh.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ecdh.js","sourceRoot":"","sources":["../src/ecdh.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAEzC;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,YAAwB,EACxB,cAA0B;IAE1B,MAAM,CAAC,GAAG,MAAM,YAAY,EAAE,CAAC;IAC/B,OAAO,CAAC,CAAC,iBAAiB,CAAC,YAAY,EAAE,cAAc,CAAC,CAAC;AAC3D,CAAC"}
package/dist/hkdf.d.ts ADDED
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Derive symmetric keys from ECDH shared secret.
3
+ * Returns { sendKey, recvKey } each 32 bytes for AEAD cipher.
4
+ */
5
+ export declare function deriveSessionKeys(sharedSecret: Uint8Array, salt?: Uint8Array, info?: string): Promise<{
6
+ sendKey: Uint8Array;
7
+ recvKey: Uint8Array;
8
+ }>;
9
+ /**
10
+ * Derive a single chain key for ratchet progression.
11
+ */
12
+ export declare function deriveChainKey(chainKey: Uint8Array, info?: string): Promise<{
13
+ messageKey: Uint8Array;
14
+ nextChainKey: Uint8Array;
15
+ }>;
16
+ //# sourceMappingURL=hkdf.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hkdf.d.ts","sourceRoot":"","sources":["../src/hkdf.ts"],"names":[],"mappings":"AAkCA;;;GAGG;AACH,wBAAsB,iBAAiB,CACrC,YAAY,EAAE,UAAU,EACxB,IAAI,GAAE,UAA8B,EACpC,IAAI,GAAE,MAAmB,GACxB,OAAO,CAAC;IAAE,OAAO,EAAE,UAAU,CAAC;IAAC,OAAO,EAAE,UAAU,CAAA;CAAE,CAAC,CASvD;AAED;;GAEG;AACH,wBAAsB,cAAc,CAClC,QAAQ,EAAE,UAAU,EACpB,IAAI,GAAE,MAAsB,GAC3B,OAAO,CAAC;IAAE,UAAU,EAAE,UAAU,CAAC;IAAC,YAAY,EAAE,UAAU,CAAA;CAAE,CAAC,CAS/D"}
package/dist/hkdf.js ADDED
@@ -0,0 +1,54 @@
1
+ import { ensureSodium } from './keys.js';
2
+ const HKDF_HASH_LEN = 32; // SHA-256
3
+ /**
4
+ * HKDF-Extract: PRK = HMAC-SHA256(salt, IKM)
5
+ */
6
+ async function hkdfExtract(salt, ikm) {
7
+ const s = await ensureSodium();
8
+ const key = salt.length > 0 ? salt : new Uint8Array(HKDF_HASH_LEN);
9
+ return s.crypto_auth_hmacsha256(ikm, key);
10
+ }
11
+ /**
12
+ * HKDF-Expand: OKM = T(1) || T(2) || ... truncated to length
13
+ */
14
+ async function hkdfExpand(prk, info, length) {
15
+ const s = await ensureSodium();
16
+ const n = Math.ceil(length / HKDF_HASH_LEN);
17
+ const okm = new Uint8Array(n * HKDF_HASH_LEN);
18
+ let prev = new Uint8Array(0);
19
+ for (let i = 1; i <= n; i++) {
20
+ const input = new Uint8Array(prev.length + info.length + 1);
21
+ input.set(prev, 0);
22
+ input.set(info, prev.length);
23
+ input[prev.length + info.length] = i;
24
+ prev = new Uint8Array(s.crypto_auth_hmacsha256(input, prk));
25
+ okm.set(prev, (i - 1) * HKDF_HASH_LEN);
26
+ }
27
+ return okm.slice(0, length);
28
+ }
29
+ /**
30
+ * Derive symmetric keys from ECDH shared secret.
31
+ * Returns { sendKey, recvKey } each 32 bytes for AEAD cipher.
32
+ */
33
+ export async function deriveSessionKeys(sharedSecret, salt = new Uint8Array(0), info = 'reley-v1') {
34
+ const infoBytes = new TextEncoder().encode(info);
35
+ const prk = await hkdfExtract(salt, sharedSecret);
36
+ // 64 bytes: first 32 = send key, next 32 = recv key
37
+ const okm = await hkdfExpand(prk, infoBytes, 64);
38
+ return {
39
+ sendKey: okm.slice(0, 32),
40
+ recvKey: okm.slice(32, 64),
41
+ };
42
+ }
43
+ /**
44
+ * Derive a single chain key for ratchet progression.
45
+ */
46
+ export async function deriveChainKey(chainKey, info = 'reley-chain') {
47
+ const s = await ensureSodium();
48
+ const msgKeyInput = new TextEncoder().encode(info + '-msg');
49
+ const chainKeyInput = new TextEncoder().encode(info + '-chain');
50
+ const messageKey = s.crypto_auth_hmacsha256(msgKeyInput, chainKey);
51
+ const nextChainKey = s.crypto_auth_hmacsha256(chainKeyInput, chainKey);
52
+ return { messageKey, nextChainKey };
53
+ }
54
+ //# sourceMappingURL=hkdf.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hkdf.js","sourceRoot":"","sources":["../src/hkdf.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ export { ensureSodium, generateIdentityKeyPair, generateEphemeralKeyPair, ed25519ToX25519Public, ed25519ToX25519Secret, generateOneTimeCode, sign, verify, type Ed25519KeyPair, type X25519KeyPair, } from './keys.js';
2
+ export { computeSharedSecret } from './ecdh.js';
3
+ export { deriveSessionKeys, deriveChainKey } from './hkdf.js';
4
+ export { encrypt, decrypt, NONCE_LENGTH, TAG_LENGTH, KEY_LENGTH, } from './cipher.js';
5
+ export { initRatchet, ratchetEncrypt, ratchetDecrypt, needsKeyRotation, KEY_ROTATION_INTERVAL, type RatchetState, } from './ratchet.js';
6
+ export { encodeQRPayload, decodeQRPayload, hashOneTimeCode, type QRPayload, } from './qr-payload.js';
7
+ export { computeFingerprint, toBase64Url, fromBase64Url, } from './utils.js';
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,YAAY,EACZ,uBAAuB,EACvB,wBAAwB,EACxB,qBAAqB,EACrB,qBAAqB,EACrB,mBAAmB,EACnB,IAAI,EACJ,MAAM,EACN,KAAK,cAAc,EACnB,KAAK,aAAa,GACnB,MAAM,WAAW,CAAC;AAEnB,OAAO,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAC;AAEhD,OAAO,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAE9D,OAAO,EACL,OAAO,EACP,OAAO,EACP,YAAY,EACZ,UAAU,EACV,UAAU,GACX,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,WAAW,EACX,cAAc,EACd,cAAc,EACd,gBAAgB,EAChB,qBAAqB,EACrB,KAAK,YAAY,GAClB,MAAM,cAAc,CAAC;AAEtB,OAAO,EACL,eAAe,EACf,eAAe,EACf,eAAe,EACf,KAAK,SAAS,GACf,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EACL,kBAAkB,EAClB,WAAW,EACX,aAAa,GACd,MAAM,YAAY,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,8 @@
1
+ export { ensureSodium, generateIdentityKeyPair, generateEphemeralKeyPair, ed25519ToX25519Public, ed25519ToX25519Secret, generateOneTimeCode, sign, verify, } from './keys.js';
2
+ export { computeSharedSecret } from './ecdh.js';
3
+ export { deriveSessionKeys, deriveChainKey } from './hkdf.js';
4
+ export { encrypt, decrypt, NONCE_LENGTH, TAG_LENGTH, KEY_LENGTH, } from './cipher.js';
5
+ export { initRatchet, ratchetEncrypt, ratchetDecrypt, needsKeyRotation, KEY_ROTATION_INTERVAL, } from './ratchet.js';
6
+ export { encodeQRPayload, decodeQRPayload, hashOneTimeCode, } from './qr-payload.js';
7
+ export { computeFingerprint, toBase64Url, fromBase64Url, } from './utils.js';
8
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,YAAY,EACZ,uBAAuB,EACvB,wBAAwB,EACxB,qBAAqB,EACrB,qBAAqB,EACrB,mBAAmB,EACnB,IAAI,EACJ,MAAM,GAGP,MAAM,WAAW,CAAC;AAEnB,OAAO,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAC;AAEhD,OAAO,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAE9D,OAAO,EACL,OAAO,EACP,OAAO,EACP,YAAY,EACZ,UAAU,EACV,UAAU,GACX,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,WAAW,EACX,cAAc,EACd,cAAc,EACd,gBAAgB,EAChB,qBAAqB,GAEtB,MAAM,cAAc,CAAC;AAEtB,OAAO,EACL,eAAe,EACf,eAAe,EACf,eAAe,GAEhB,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EACL,kBAAkB,EAClB,WAAW,EACX,aAAa,GACd,MAAM,YAAY,CAAC"}
package/dist/keys.d.ts ADDED
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Key generation, signing, and libsodium initialization.
3
+ *
4
+ * Uses a default import of libsodium-wrappers-sumo which works across
5
+ * environments:
6
+ * - Node.js: vitest alias / bundler resolves to the CJS module
7
+ * - Browser: bundlers (webpack/vite) handle ESM resolution
8
+ */
9
+ import _sodium from 'libsodium-wrappers-sumo';
10
+ /**
11
+ * Ensure libsodium is loaded and ready.
12
+ */
13
+ export declare function ensureSodium(): Promise<typeof _sodium>;
14
+ export interface Ed25519KeyPair {
15
+ publicKey: Uint8Array;
16
+ secretKey: Uint8Array;
17
+ keyType: 'ed25519';
18
+ }
19
+ export interface X25519KeyPair {
20
+ publicKey: Uint8Array;
21
+ secretKey: Uint8Array;
22
+ keyType: 'x25519';
23
+ }
24
+ /**
25
+ * Generate an Ed25519 identity key pair (long-term, per-device).
26
+ */
27
+ export declare function generateIdentityKeyPair(): Promise<Ed25519KeyPair>;
28
+ /**
29
+ * Generate an X25519 ephemeral key pair (per-session).
30
+ */
31
+ export declare function generateEphemeralKeyPair(): Promise<X25519KeyPair>;
32
+ /**
33
+ * Convert an Ed25519 public key to X25519 for ECDH.
34
+ */
35
+ export declare function ed25519ToX25519Public(ed25519Pk: Uint8Array): Promise<Uint8Array>;
36
+ /**
37
+ * Convert an Ed25519 secret key to X25519 for ECDH.
38
+ */
39
+ export declare function ed25519ToX25519Secret(ed25519Sk: Uint8Array): Promise<Uint8Array>;
40
+ /**
41
+ * Generate a cryptographically secure random one-time code.
42
+ */
43
+ export declare function generateOneTimeCode(length?: number): Promise<Uint8Array>;
44
+ /**
45
+ * Sign a message with an Ed25519 secret key.
46
+ */
47
+ export declare function sign(message: Uint8Array, secretKey: Uint8Array): Promise<Uint8Array>;
48
+ /**
49
+ * Verify an Ed25519 signature.
50
+ */
51
+ export declare function verify(signature: Uint8Array, message: Uint8Array, publicKey: Uint8Array): Promise<boolean>;
52
+ //# sourceMappingURL=keys.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"keys.d.ts","sourceRoot":"","sources":["../src/keys.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,OAAO,MAAM,yBAAyB,CAAC;AAI9C;;GAEG;AACH,wBAAsB,YAAY,IAAI,OAAO,CAAC,OAAO,OAAO,CAAC,CAM5D;AAED,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,UAAU,CAAC;IACtB,SAAS,EAAE,UAAU,CAAC;IACtB,OAAO,EAAE,SAAS,CAAC;CACpB;AAED,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,UAAU,CAAC;IACtB,SAAS,EAAE,UAAU,CAAC;IACtB,OAAO,EAAE,QAAQ,CAAC;CACnB;AAED;;GAEG;AACH,wBAAsB,uBAAuB,IAAI,OAAO,CAAC,cAAc,CAAC,CAQvE;AAED;;GAEG;AACH,wBAAsB,wBAAwB,IAAI,OAAO,CAAC,aAAa,CAAC,CAQvE;AAED;;GAEG;AACH,wBAAsB,qBAAqB,CAAC,SAAS,EAAE,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC,CAGtF;AAED;;GAEG;AACH,wBAAsB,qBAAqB,CAAC,SAAS,EAAE,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC,CAGtF;AAED;;GAEG;AACH,wBAAsB,mBAAmB,CAAC,MAAM,GAAE,MAAW,GAAG,OAAO,CAAC,UAAU,CAAC,CAGlF;AAED;;GAEG;AACH,wBAAsB,IAAI,CAAC,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC,CAG1F;AAED;;GAEG;AACH,wBAAsB,MAAM,CAC1B,SAAS,EAAE,UAAU,EACrB,OAAO,EAAE,UAAU,EACnB,SAAS,EAAE,UAAU,GACpB,OAAO,CAAC,OAAO,CAAC,CAGlB"}
package/dist/keys.js ADDED
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Key generation, signing, and libsodium initialization.
3
+ *
4
+ * Uses a default import of libsodium-wrappers-sumo which works across
5
+ * environments:
6
+ * - Node.js: vitest alias / bundler resolves to the CJS module
7
+ * - Browser: bundlers (webpack/vite) handle ESM resolution
8
+ */
9
+ import _sodium from 'libsodium-wrappers-sumo';
10
+ let initialized = false;
11
+ /**
12
+ * Ensure libsodium is loaded and ready.
13
+ */
14
+ export async function ensureSodium() {
15
+ if (!initialized) {
16
+ await _sodium.ready;
17
+ initialized = true;
18
+ }
19
+ return _sodium;
20
+ }
21
+ /**
22
+ * Generate an Ed25519 identity key pair (long-term, per-device).
23
+ */
24
+ export async function generateIdentityKeyPair() {
25
+ const s = await ensureSodium();
26
+ const kp = s.crypto_sign_keypair();
27
+ return {
28
+ publicKey: kp.publicKey,
29
+ secretKey: kp.privateKey,
30
+ keyType: 'ed25519',
31
+ };
32
+ }
33
+ /**
34
+ * Generate an X25519 ephemeral key pair (per-session).
35
+ */
36
+ export async function generateEphemeralKeyPair() {
37
+ const s = await ensureSodium();
38
+ const kp = s.crypto_kx_keypair();
39
+ return {
40
+ publicKey: kp.publicKey,
41
+ secretKey: kp.privateKey,
42
+ keyType: 'x25519',
43
+ };
44
+ }
45
+ /**
46
+ * Convert an Ed25519 public key to X25519 for ECDH.
47
+ */
48
+ export async function ed25519ToX25519Public(ed25519Pk) {
49
+ const s = await ensureSodium();
50
+ return s.crypto_sign_ed25519_pk_to_curve25519(ed25519Pk);
51
+ }
52
+ /**
53
+ * Convert an Ed25519 secret key to X25519 for ECDH.
54
+ */
55
+ export async function ed25519ToX25519Secret(ed25519Sk) {
56
+ const s = await ensureSodium();
57
+ return s.crypto_sign_ed25519_sk_to_curve25519(ed25519Sk);
58
+ }
59
+ /**
60
+ * Generate a cryptographically secure random one-time code.
61
+ */
62
+ export async function generateOneTimeCode(length = 32) {
63
+ const s = await ensureSodium();
64
+ return s.randombytes_buf(length);
65
+ }
66
+ /**
67
+ * Sign a message with an Ed25519 secret key.
68
+ */
69
+ export async function sign(message, secretKey) {
70
+ const s = await ensureSodium();
71
+ return s.crypto_sign_detached(message, secretKey);
72
+ }
73
+ /**
74
+ * Verify an Ed25519 signature.
75
+ */
76
+ export async function verify(signature, message, publicKey) {
77
+ const s = await ensureSodium();
78
+ return s.crypto_sign_verify_detached(signature, message, publicKey);
79
+ }
80
+ //# sourceMappingURL=keys.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"keys.js","sourceRoot":"","sources":["../src/keys.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,OAAO,MAAM,yBAAyB,CAAC;AAE9C,IAAI,WAAW,GAAG,KAAK,CAAC;AAExB;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY;IAChC,IAAI,CAAC,WAAW,EAAE,CAAC;QACjB,MAAM,OAAO,CAAC,KAAK,CAAC;QACpB,WAAW,GAAG,IAAI,CAAC;IACrB,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAcD;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,uBAAuB;IAC3C,MAAM,CAAC,GAAG,MAAM,YAAY,EAAE,CAAC;IAC/B,MAAM,EAAE,GAAG,CAAC,CAAC,mBAAmB,EAAE,CAAC;IACnC,OAAO;QACL,SAAS,EAAE,EAAE,CAAC,SAAS;QACvB,SAAS,EAAE,EAAE,CAAC,UAAU;QACxB,OAAO,EAAE,SAAS;KACnB,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,wBAAwB;IAC5C,MAAM,CAAC,GAAG,MAAM,YAAY,EAAE,CAAC;IAC/B,MAAM,EAAE,GAAG,CAAC,CAAC,iBAAiB,EAAE,CAAC;IACjC,OAAO;QACL,SAAS,EAAE,EAAE,CAAC,SAAS;QACvB,SAAS,EAAE,EAAE,CAAC,UAAU;QACxB,OAAO,EAAE,QAAQ;KAClB,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAAC,SAAqB;IAC/D,MAAM,CAAC,GAAG,MAAM,YAAY,EAAE,CAAC;IAC/B,OAAO,CAAC,CAAC,oCAAoC,CAAC,SAAS,CAAC,CAAC;AAC3D,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAAC,SAAqB;IAC/D,MAAM,CAAC,GAAG,MAAM,YAAY,EAAE,CAAC;IAC/B,OAAO,CAAC,CAAC,oCAAoC,CAAC,SAAS,CAAC,CAAC;AAC3D,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,SAAiB,EAAE;IAC3D,MAAM,CAAC,GAAG,MAAM,YAAY,EAAE,CAAC;IAC/B,OAAO,CAAC,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC;AACnC,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,IAAI,CAAC,OAAmB,EAAE,SAAqB;IACnE,MAAM,CAAC,GAAG,MAAM,YAAY,EAAE,CAAC;IAC/B,OAAO,CAAC,CAAC,oBAAoB,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;AACpD,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,MAAM,CAC1B,SAAqB,EACrB,OAAmB,EACnB,SAAqB;IAErB,MAAM,CAAC,GAAG,MAAM,YAAY,EAAE,CAAC;IAC/B,OAAO,CAAC,CAAC,2BAA2B,CAAC,SAAS,EAAE,OAAO,EAAE,SAAS,CAAC,CAAC;AACtE,CAAC"}
@@ -0,0 +1,19 @@
1
+ export interface QRPayload {
2
+ releyUrl: string;
3
+ publicKey: Uint8Array;
4
+ oneTimeCode: Uint8Array;
5
+ jwt: string;
6
+ }
7
+ /**
8
+ * Encode a QR payload to a base64url string.
9
+ */
10
+ export declare function encodeQRPayload(payload: QRPayload): Promise<string>;
11
+ /**
12
+ * Decode a QR payload from a base64url string.
13
+ */
14
+ export declare function decodeQRPayload(encoded: string): Promise<QRPayload>;
15
+ /**
16
+ * Hash a one-time code (for safe transmission to reley).
17
+ */
18
+ export declare function hashOneTimeCode(otc: Uint8Array): Promise<Uint8Array>;
19
+ //# sourceMappingURL=qr-payload.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"qr-payload.d.ts","sourceRoot":"","sources":["../src/qr-payload.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,SAAS;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,UAAU,CAAC;IACtB,WAAW,EAAE,UAAU,CAAC;IACxB,GAAG,EAAE,MAAM,CAAC;CACb;AAID;;GAEG;AACH,wBAAsB,eAAe,CAAC,OAAO,EAAE,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC,CAqCzE;AAED;;GAEG;AACH,wBAAsB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC,CA+CzE;AAED;;GAEG;AACH,wBAAsB,eAAe,CAAC,GAAG,EAAE,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC,CAG1E"}
@@ -0,0 +1,84 @@
1
+ import { ensureSodium } from './keys.js';
2
+ const MAGIC = 'CB1'; // Reley v1
3
+ /**
4
+ * Encode a QR payload to a base64url string.
5
+ */
6
+ export async function encodeQRPayload(payload) {
7
+ const s = await ensureSodium();
8
+ const releyBytes = new TextEncoder().encode(payload.releyUrl);
9
+ const jwtBytes = new TextEncoder().encode(payload.jwt);
10
+ // Format: MAGIC(3) + releyLen(2 BE) + reley + pubKey(32) + otc(32) + jwtLen(2 BE) + jwt
11
+ const totalLen = 3 + 2 + releyBytes.length + 32 + 32 + 2 + jwtBytes.length;
12
+ const buf = new Uint8Array(totalLen);
13
+ let offset = 0;
14
+ // Magic
15
+ buf[offset++] = MAGIC.charCodeAt(0);
16
+ buf[offset++] = MAGIC.charCodeAt(1);
17
+ buf[offset++] = MAGIC.charCodeAt(2);
18
+ // Reley URL length + data
19
+ buf[offset++] = (releyBytes.length >>> 8) & 0xff;
20
+ buf[offset++] = releyBytes.length & 0xff;
21
+ buf.set(releyBytes, offset);
22
+ offset += releyBytes.length;
23
+ // Public key (32 bytes)
24
+ buf.set(payload.publicKey, offset);
25
+ offset += 32;
26
+ // One-time code (32 bytes)
27
+ buf.set(payload.oneTimeCode, offset);
28
+ offset += 32;
29
+ // JWT length + data
30
+ buf[offset++] = (jwtBytes.length >>> 8) & 0xff;
31
+ buf[offset++] = jwtBytes.length & 0xff;
32
+ buf.set(jwtBytes, offset);
33
+ return s.to_base64(buf, s.base64_variants.URLSAFE_NO_PADDING);
34
+ }
35
+ /**
36
+ * Decode a QR payload from a base64url string.
37
+ */
38
+ export async function decodeQRPayload(encoded) {
39
+ const s = await ensureSodium();
40
+ const buf = s.from_base64(encoded, s.base64_variants.URLSAFE_NO_PADDING);
41
+ // Minimum: MAGIC(3) + releyLen(2) + pubKey(32) + otc(32) + jwtLen(2) = 71 bytes
42
+ if (buf.length < 71) {
43
+ throw new Error(`QR payload too short: ${buf.length} bytes (minimum 71)`);
44
+ }
45
+ let offset = 0;
46
+ // Verify magic
47
+ const magic = String.fromCharCode(buf[offset++], buf[offset++], buf[offset++]);
48
+ if (magic !== MAGIC) {
49
+ throw new Error(`Invalid QR payload magic: ${magic}`);
50
+ }
51
+ // Reley URL
52
+ const releyLen = (buf[offset] << 8) | buf[offset + 1];
53
+ offset += 2;
54
+ if (offset + releyLen + 32 + 32 + 2 > buf.length) {
55
+ throw new Error('QR payload truncated: reley URL overflows buffer');
56
+ }
57
+ const releyUrl = new TextDecoder().decode(buf.slice(offset, offset + releyLen));
58
+ offset += releyLen;
59
+ // Public key (32 bytes)
60
+ const publicKey = buf.slice(offset, offset + 32);
61
+ offset += 32;
62
+ // One-time code (32 bytes)
63
+ const oneTimeCode = buf.slice(offset, offset + 32);
64
+ offset += 32;
65
+ // JWT
66
+ if (offset + 2 > buf.length) {
67
+ throw new Error('QR payload truncated: missing JWT length');
68
+ }
69
+ const jwtLen = (buf[offset] << 8) | buf[offset + 1];
70
+ offset += 2;
71
+ if (offset + jwtLen > buf.length) {
72
+ throw new Error('QR payload truncated: JWT overflows buffer');
73
+ }
74
+ const jwt = new TextDecoder().decode(buf.slice(offset, offset + jwtLen));
75
+ return { releyUrl, publicKey, oneTimeCode, jwt };
76
+ }
77
+ /**
78
+ * Hash a one-time code (for safe transmission to reley).
79
+ */
80
+ export async function hashOneTimeCode(otc) {
81
+ const s = await ensureSodium();
82
+ return s.crypto_generichash(32, otc, null);
83
+ }
84
+ //# sourceMappingURL=qr-payload.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"qr-payload.js","sourceRoot":"","sources":["../src/qr-payload.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,34 @@
1
+ export declare const KEY_ROTATION_INTERVAL = 50;
2
+ export interface RatchetState {
3
+ sendChainKey: Uint8Array;
4
+ recvChainKey: Uint8Array;
5
+ sendCounter: number;
6
+ recvCounter: number;
7
+ /** Highest received counter for replay protection */
8
+ maxRecvCounter: number;
9
+ }
10
+ /**
11
+ * Initialize a ratchet state from derived session keys.
12
+ */
13
+ export declare function initRatchet(sendKey: Uint8Array, recvKey: Uint8Array): RatchetState;
14
+ /**
15
+ * Encrypt a message and advance the send ratchet.
16
+ */
17
+ export declare function ratchetEncrypt(state: RatchetState, plaintext: Uint8Array): Promise<{
18
+ ciphertext: Uint8Array;
19
+ nonce: Uint8Array;
20
+ counter: number;
21
+ state: RatchetState;
22
+ }>;
23
+ /**
24
+ * Decrypt a message and advance the receive ratchet.
25
+ */
26
+ export declare function ratchetDecrypt(state: RatchetState, ciphertext: Uint8Array, nonce: Uint8Array, counter: number): Promise<{
27
+ plaintext: Uint8Array;
28
+ state: RatchetState;
29
+ }>;
30
+ /**
31
+ * Check if key rotation is needed.
32
+ */
33
+ export declare function needsKeyRotation(state: RatchetState): boolean;
34
+ //# sourceMappingURL=ratchet.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ratchet.d.ts","sourceRoot":"","sources":["../src/ratchet.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,qBAAqB,KAAK,CAAC;AAExC,MAAM,WAAW,YAAY;IAC3B,YAAY,EAAE,UAAU,CAAC;IACzB,YAAY,EAAE,UAAU,CAAC;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,qDAAqD;IACrD,cAAc,EAAE,MAAM,CAAC;CACxB;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,UAAU,GAAG,YAAY,CAQlF;AAED;;GAEG;AACH,wBAAsB,cAAc,CAClC,KAAK,EAAE,YAAY,EACnB,SAAS,EAAE,UAAU,GACpB,OAAO,CAAC;IAAE,UAAU,EAAE,UAAU,CAAC;IAAC,KAAK,EAAE,UAAU,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,YAAY,CAAA;CAAE,CAAC,CAgB9F;AAED;;GAEG;AACH,wBAAsB,cAAc,CAClC,KAAK,EAAE,YAAY,EACnB,UAAU,EAAE,UAAU,EACtB,KAAK,EAAE,UAAU,EACjB,OAAO,EAAE,MAAM,GACd,OAAO,CAAC;IAAE,SAAS,EAAE,UAAU,CAAC;IAAC,KAAK,EAAE,YAAY,CAAA;CAAE,CAAC,CA2CzD;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAE7D"}
@@ -0,0 +1,91 @@
1
+ import { deriveChainKey } from './hkdf.js';
2
+ import { encrypt, decrypt } from './cipher.js';
3
+ export const KEY_ROTATION_INTERVAL = 50;
4
+ /**
5
+ * Initialize a ratchet state from derived session keys.
6
+ */
7
+ export function initRatchet(sendKey, recvKey) {
8
+ return {
9
+ sendChainKey: sendKey,
10
+ recvChainKey: recvKey,
11
+ sendCounter: 0,
12
+ recvCounter: 0,
13
+ maxRecvCounter: -1,
14
+ };
15
+ }
16
+ /**
17
+ * Encrypt a message and advance the send ratchet.
18
+ */
19
+ export async function ratchetEncrypt(state, plaintext) {
20
+ const { messageKey, nextChainKey } = await deriveChainKey(state.sendChainKey);
21
+ const counter = state.sendCounter;
22
+ // Build AAD: counter as 4-byte big-endian
23
+ const aad = buildAAD(1, counter);
24
+ const { nonce, ciphertext } = await encrypt(plaintext, messageKey, aad);
25
+ const newState = {
26
+ ...state,
27
+ sendChainKey: nextChainKey,
28
+ sendCounter: counter + 1,
29
+ };
30
+ return { ciphertext, nonce, counter, state: newState };
31
+ }
32
+ /**
33
+ * Decrypt a message and advance the receive ratchet.
34
+ */
35
+ export async function ratchetDecrypt(state, ciphertext, nonce, counter) {
36
+ // Replay protection: reject messages with counter <= maxRecvCounter
37
+ if (counter <= state.maxRecvCounter) {
38
+ throw new Error(`Replay attack detected: counter ${counter} <= ${state.maxRecvCounter}`);
39
+ }
40
+ // Prevent DoS via excessively large counter gaps.
41
+ // Over a WebSocket (TCP-ordered), gaps should never exceed a few messages.
42
+ const MAX_COUNTER_GAP = 1000;
43
+ const gap = counter - state.recvCounter;
44
+ if (gap > MAX_COUNTER_GAP) {
45
+ throw new Error(`Counter gap too large: ${gap} (max ${MAX_COUNTER_GAP})`);
46
+ }
47
+ // Advance the recv chain to the correct counter
48
+ let chainKey = state.recvChainKey;
49
+ let currentCounter = state.recvCounter;
50
+ let messageKey;
51
+ while (currentCounter <= counter) {
52
+ const derived = await deriveChainKey(chainKey);
53
+ if (currentCounter === counter) {
54
+ messageKey = derived.messageKey;
55
+ }
56
+ chainKey = derived.nextChainKey;
57
+ currentCounter++;
58
+ }
59
+ if (!messageKey) {
60
+ throw new Error('Failed to derive message key');
61
+ }
62
+ const aad = buildAAD(1, counter);
63
+ const plaintext = await decrypt(ciphertext, nonce, messageKey, aad);
64
+ const newState = {
65
+ ...state,
66
+ recvChainKey: chainKey,
67
+ recvCounter: currentCounter,
68
+ maxRecvCounter: counter,
69
+ };
70
+ return { plaintext, state: newState };
71
+ }
72
+ /**
73
+ * Check if key rotation is needed.
74
+ */
75
+ export function needsKeyRotation(state) {
76
+ return state.sendCounter > 0 && state.sendCounter % KEY_ROTATION_INTERVAL === 0;
77
+ }
78
+ /**
79
+ * Build AAD bytes: version (1 byte) + type (1 byte) + counter (4 bytes BE)
80
+ */
81
+ function buildAAD(version, counter) {
82
+ const aad = new Uint8Array(6);
83
+ aad[0] = version;
84
+ aad[1] = 0x01; // message type
85
+ aad[2] = (counter >>> 24) & 0xff;
86
+ aad[3] = (counter >>> 16) & 0xff;
87
+ aad[4] = (counter >>> 8) & 0xff;
88
+ aad[5] = counter & 0xff;
89
+ return aad;
90
+ }
91
+ //# sourceMappingURL=ratchet.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ratchet.js","sourceRoot":"","sources":["../src/ratchet.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Compute a human-readable fingerprint from two public keys for MITM verification.
3
+ * Keys are sorted lexicographically to ensure both parties produce the same fingerprint.
4
+ * Uses Blake2b-128 hash, formatted as uppercase hex groups: "XXXX-XXXX-..."
5
+ */
6
+ export declare function computeFingerprint(pk1: Uint8Array, pk2: Uint8Array): Promise<string>;
7
+ /**
8
+ * Encode a Uint8Array to a base64url string (no padding).
9
+ */
10
+ export declare function toBase64Url(data: Uint8Array): Promise<string>;
11
+ /**
12
+ * Decode a base64url string to a Uint8Array.
13
+ */
14
+ export declare function fromBase64Url(str: string): Promise<Uint8Array>;
15
+ //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,wBAAsB,kBAAkB,CACtC,GAAG,EAAE,UAAU,EACf,GAAG,EAAE,UAAU,GACd,OAAO,CAAC,MAAM,CAAC,CAcjB;AAED;;GAEG;AACH,wBAAsB,WAAW,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAGnE;AAED;;GAEG;AACH,wBAAsB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAGpE"}