@towns-labs/sdk-crypto 2.0.1

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/README.md ADDED
@@ -0,0 +1,5 @@
1
+ # @towns-labs/sdk-crypto
2
+
3
+ This package provides a set of cryptographic functions used in the Towns Protocol SDK.
4
+
5
+ It's required to be a package so we can properly bundle the code for the web and node.
@@ -0,0 +1,30 @@
1
+ //#region rolldown:runtime
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __copyProps = (to, from, except, desc) => {
9
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
10
+ key = keys[i];
11
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
12
+ get: ((k) => from[k]).bind(null, key),
13
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
14
+ });
15
+ }
16
+ return to;
17
+ };
18
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
19
+ value: mod,
20
+ enumerable: true
21
+ }) : target, mod));
22
+
23
+ //#endregion
24
+
25
+ Object.defineProperty(exports, '__toESM', {
26
+ enumerable: true,
27
+ get: function () {
28
+ return __toESM;
29
+ }
30
+ });
@@ -0,0 +1,112 @@
1
+ const require_chunk = require('../chunk-CUT6urMc.cjs');
2
+ const __towns_labs_utils = require_chunk.__toESM(require("@towns-labs/utils"));
3
+ const __towns_labs_proto = require_chunk.__toESM(require("@towns-labs/proto"));
4
+ const __towns_labs_encryption = require_chunk.__toESM(require("@towns-labs/encryption"));
5
+ const node_crypto = require_chunk.__toESM(require("node:crypto"));
6
+
7
+ //#region src/node/index.ts
8
+ function uint8ArrayToBase64(uint8Array) {
9
+ return Buffer.from(uint8Array).toString("base64");
10
+ }
11
+ function base64ToUint8Array(base64) {
12
+ const buffer = Buffer.from(base64, "base64");
13
+ return new Uint8Array(buffer);
14
+ }
15
+ function bufferToUint8Array(buffer) {
16
+ return new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength);
17
+ }
18
+ function uint8ArrayToBuffer(uint8Array) {
19
+ return Buffer.from(uint8Array.buffer, uint8Array.byteOffset, uint8Array.byteLength);
20
+ }
21
+ async function getExtendedKeyMaterial(seedBuffer, length) {
22
+ const hash = node_crypto.default.createHash("sha256");
23
+ hash.update(uint8ArrayToBuffer(seedBuffer));
24
+ let keyMaterial = bufferToUint8Array(hash.digest());
25
+ while (keyMaterial.length < length) {
26
+ const newHash = node_crypto.default.createHash("sha256");
27
+ newHash.update(uint8ArrayToBuffer(keyMaterial));
28
+ keyMaterial = new Uint8Array([...keyMaterial, ...bufferToUint8Array(newHash.digest())]);
29
+ }
30
+ return keyMaterial.slice(0, length);
31
+ }
32
+ async function deriveKeyAndIV(keyPhrase) {
33
+ let keyBuffer;
34
+ if (typeof keyPhrase === "string") {
35
+ const encoder = new TextEncoder();
36
+ keyBuffer = encoder.encode(keyPhrase);
37
+ } else keyBuffer = keyPhrase;
38
+ const keyMaterial = await getExtendedKeyMaterial(keyBuffer, 44);
39
+ const key = keyMaterial.slice(0, 32);
40
+ const iv = keyMaterial.slice(32, 44);
41
+ return {
42
+ key,
43
+ iv
44
+ };
45
+ }
46
+ async function encryptAESGCM(data, key, iv) {
47
+ if (!data || data.length === 0) throw new Error("Cannot encrypt undefined or empty data");
48
+ if (!key) key = node_crypto.default.randomBytes(32);
49
+ else if (key.length !== 32) throw new Error("Invalid key length. AES-256-GCM requires a 32-byte key.");
50
+ if (!iv) iv = node_crypto.default.randomBytes(12);
51
+ else if (iv.length !== 12) throw new Error("Invalid IV length. AES-256-GCM requires a 12-byte IV.");
52
+ const cipher = node_crypto.default.createCipheriv("aes-256-gcm", uint8ArrayToBuffer(key), uint8ArrayToBuffer(iv));
53
+ const encrypted = Buffer.concat([cipher.update(uint8ArrayToBuffer(data)), cipher.final()]);
54
+ const authTag = cipher.getAuthTag();
55
+ const ciphertext = Buffer.concat([encrypted, authTag]);
56
+ return {
57
+ ciphertext: bufferToUint8Array(ciphertext),
58
+ iv,
59
+ secretKey: key
60
+ };
61
+ }
62
+ async function decryptAESGCM(data, key, iv) {
63
+ if (key.length !== 32) throw new Error("Invalid key length. AES-256-GCM requires a 32-byte key.");
64
+ if (iv.length !== 12) throw new Error("Invalid IV length. AES-256-GCM requires a 12-byte IV.");
65
+ let dataBuffer;
66
+ if (typeof data === "string") dataBuffer = Buffer.from(data, "base64");
67
+ else dataBuffer = data;
68
+ const encryptedBuffer = Buffer.from(dataBuffer.buffer, dataBuffer.byteOffset, dataBuffer.byteLength);
69
+ const authTag = new Uint8Array(encryptedBuffer.buffer.slice(encryptedBuffer.byteOffset + encryptedBuffer.length - 16, encryptedBuffer.byteOffset + encryptedBuffer.length));
70
+ const encryptedContent = new Uint8Array(encryptedBuffer.buffer.slice(encryptedBuffer.byteOffset, encryptedBuffer.byteOffset + encryptedBuffer.length - 16));
71
+ const decipher = node_crypto.default.createDecipheriv("aes-256-gcm", key, iv);
72
+ decipher.setAuthTag(authTag);
73
+ const decrypted = Buffer.concat([decipher.update(encryptedContent), decipher.final()]);
74
+ return new Uint8Array(decrypted.buffer, decrypted.byteOffset, decrypted.byteLength);
75
+ }
76
+ async function decryptDerivedAESGCM(keyPhrase, encryptedData) {
77
+ if (encryptedData.algorithm !== __towns_labs_encryption.AES_GCM_DERIVED_ALGORITHM) (0, __towns_labs_utils.throwWithCode)(`${encryptedData.algorithm}" algorithm not implemented`, __towns_labs_proto.Err.UNIMPLEMENTED);
78
+ const { key, iv } = await deriveKeyAndIV(keyPhrase);
79
+ const ciphertext = base64ToUint8Array(encryptedData.ciphertext);
80
+ return decryptAESGCM(ciphertext, key, iv);
81
+ }
82
+ async function encryptChunkedAESGCM(data, chunkSize) {
83
+ if (!data || data.length === 0) throw new Error("Cannot encrypt undefined or empty data");
84
+ const secretKey = node_crypto.default.getRandomValues(new Uint8Array(32));
85
+ const maxPlaintextSize = chunkSize - 16;
86
+ const chunks = [];
87
+ let offset = 0;
88
+ while (offset < data.length) {
89
+ const dataChunk = data.slice(offset, offset + maxPlaintextSize);
90
+ const { ciphertext, iv } = await encryptAESGCM(dataChunk, secretKey);
91
+ if (ciphertext.byteLength > chunkSize) throw new Error(`Encrypted chunk exceeds chunkSize. Adjust chunkSize.`);
92
+ chunks.push({
93
+ ciphertext,
94
+ iv
95
+ });
96
+ offset += maxPlaintextSize;
97
+ }
98
+ return {
99
+ chunks,
100
+ secretKey
101
+ };
102
+ }
103
+
104
+ //#endregion
105
+ exports.base64ToUint8Array = base64ToUint8Array;
106
+ exports.decryptAESGCM = decryptAESGCM;
107
+ exports.decryptDerivedAESGCM = decryptDerivedAESGCM;
108
+ exports.deriveKeyAndIV = deriveKeyAndIV;
109
+ exports.encryptAESGCM = encryptAESGCM;
110
+ exports.encryptChunkedAESGCM = encryptChunkedAESGCM;
111
+ exports.uint8ArrayToBase64 = uint8ArrayToBase64;
112
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs","names":["uint8Array: Uint8Array","base64: string","buffer: Buffer","seedBuffer: Uint8Array","length: number","keyPhrase: string | Uint8Array","keyBuffer: Uint8Array","data: Uint8Array","key?: Uint8Array","iv?: Uint8Array","data: Uint8Array | string","key: Uint8Array","iv: Uint8Array","dataBuffer: Uint8Array","keyPhrase: string","encryptedData: EncryptedData","AES_GCM_DERIVED_ALGORITHM","Err","chunkSize: number","chunks: Array<{ ciphertext: Uint8Array; iv: Uint8Array }>"],"sources":["../../src/node/index.ts"],"sourcesContent":["import { throwWithCode } from '@towns-labs/utils'\nimport { EncryptedData, Err } from '@towns-labs/proto'\nimport { AES_GCM_DERIVED_ALGORITHM } from '@towns-labs/encryption'\nimport crypto from 'node:crypto'\n\nexport function uint8ArrayToBase64(uint8Array: Uint8Array): string {\n return Buffer.from(uint8Array).toString('base64')\n}\n\nexport function base64ToUint8Array(base64: string): Uint8Array {\n const buffer = Buffer.from(base64, 'base64')\n return new Uint8Array(buffer)\n}\n\nfunction bufferToUint8Array(buffer: Buffer): Uint8Array {\n return new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength)\n}\n\nfunction uint8ArrayToBuffer(uint8Array: Uint8Array): Buffer {\n return Buffer.from(uint8Array.buffer, uint8Array.byteOffset, uint8Array.byteLength)\n}\n\nasync function getExtendedKeyMaterial(seedBuffer: Uint8Array, length: number): Promise<Uint8Array> {\n const hash = crypto.createHash('sha256')\n hash.update(uint8ArrayToBuffer(seedBuffer))\n let keyMaterial = bufferToUint8Array(hash.digest())\n\n while (keyMaterial.length < length) {\n const newHash = crypto.createHash('sha256')\n newHash.update(uint8ArrayToBuffer(keyMaterial))\n keyMaterial = new Uint8Array([...keyMaterial, ...bufferToUint8Array(newHash.digest())])\n }\n\n return keyMaterial.slice(0, length)\n}\n\nexport async function deriveKeyAndIV(\n keyPhrase: string | Uint8Array,\n): Promise<{ key: Uint8Array; iv: Uint8Array }> {\n let keyBuffer: Uint8Array\n\n if (typeof keyPhrase === 'string') {\n const encoder = new TextEncoder()\n keyBuffer = encoder.encode(keyPhrase)\n } else {\n keyBuffer = keyPhrase\n }\n\n const keyMaterial = await getExtendedKeyMaterial(keyBuffer, 32 + 12) // 32 bytes for key, 12 bytes for IV\n\n const key = keyMaterial.slice(0, 32) // AES-256 key\n const iv = keyMaterial.slice(32, 32 + 12) // AES-GCM IV\n\n return { key, iv }\n}\n\nexport async function encryptAESGCM(\n data: Uint8Array,\n key?: Uint8Array,\n iv?: Uint8Array,\n): Promise<{ ciphertext: Uint8Array; iv: Uint8Array; secretKey: Uint8Array }> {\n if (!data || data.length === 0) {\n throw new Error('Cannot encrypt undefined or empty data')\n }\n\n if (!key) {\n key = crypto.randomBytes(32)\n } else if (key.length !== 32) {\n throw new Error('Invalid key length. AES-256-GCM requires a 32-byte key.')\n }\n\n if (!iv) {\n iv = crypto.randomBytes(12)\n } else if (iv.length !== 12) {\n throw new Error('Invalid IV length. AES-256-GCM requires a 12-byte IV.')\n }\n\n const cipher = crypto.createCipheriv(\n 'aes-256-gcm',\n uint8ArrayToBuffer(key),\n uint8ArrayToBuffer(iv),\n )\n const encrypted = Buffer.concat([cipher.update(uint8ArrayToBuffer(data)), cipher.final()])\n const authTag = cipher.getAuthTag()\n const ciphertext = Buffer.concat([encrypted, authTag])\n\n return { ciphertext: bufferToUint8Array(ciphertext), iv, secretKey: key }\n}\n\nexport async function decryptAESGCM(\n data: Uint8Array | string,\n key: Uint8Array,\n iv: Uint8Array,\n): Promise<Uint8Array> {\n if (key.length !== 32) {\n throw new Error('Invalid key length. AES-256-GCM requires a 32-byte key.')\n }\n\n if (iv.length !== 12) {\n throw new Error('Invalid IV length. AES-256-GCM requires a 12-byte IV.')\n }\n\n // Convert data to Uint8Array if it is a string\n let dataBuffer: Uint8Array\n if (typeof data === 'string') {\n dataBuffer = Buffer.from(data, 'base64')\n } else {\n dataBuffer = data\n }\n\n const encryptedBuffer = Buffer.from(\n dataBuffer.buffer,\n dataBuffer.byteOffset,\n dataBuffer.byteLength,\n )\n const authTag = new Uint8Array(\n encryptedBuffer.buffer.slice(\n encryptedBuffer.byteOffset + encryptedBuffer.length - 16,\n encryptedBuffer.byteOffset + encryptedBuffer.length,\n ),\n )\n const encryptedContent = new Uint8Array(\n encryptedBuffer.buffer.slice(\n encryptedBuffer.byteOffset,\n encryptedBuffer.byteOffset + encryptedBuffer.length - 16,\n ),\n )\n\n const decipher = crypto.createDecipheriv('aes-256-gcm', key, iv)\n decipher.setAuthTag(authTag)\n\n const decrypted = Buffer.concat([decipher.update(encryptedContent), decipher.final()])\n return new Uint8Array(decrypted.buffer, decrypted.byteOffset, decrypted.byteLength)\n}\n\nexport async function decryptDerivedAESGCM(\n keyPhrase: string,\n encryptedData: EncryptedData,\n): Promise<Uint8Array> {\n if (encryptedData.algorithm !== AES_GCM_DERIVED_ALGORITHM) {\n throwWithCode(`${encryptedData.algorithm}\" algorithm not implemented`, Err.UNIMPLEMENTED)\n }\n const { key, iv } = await deriveKeyAndIV(keyPhrase)\n const ciphertext = base64ToUint8Array(encryptedData.ciphertext)\n return decryptAESGCM(ciphertext, key, iv)\n}\n\nexport async function encryptChunkedAESGCM(\n data: Uint8Array,\n chunkSize: number,\n): Promise<{\n chunks: Array<{ ciphertext: Uint8Array; iv: Uint8Array }>\n secretKey: Uint8Array\n}> {\n if (!data || data.length === 0) {\n throw new Error('Cannot encrypt undefined or empty data')\n }\n const secretKey = crypto.getRandomValues(new Uint8Array(32))\n // Adjust chunk size to account for AES-GCM overhead (16-byte auth tag)\n const maxPlaintextSize = chunkSize - 16\n const chunks: Array<{ ciphertext: Uint8Array; iv: Uint8Array }> = []\n\n let offset = 0\n while (offset < data.length) {\n const dataChunk = data.slice(offset, offset + maxPlaintextSize)\n const { ciphertext, iv } = await encryptAESGCM(dataChunk, secretKey)\n\n if (ciphertext.byteLength > chunkSize) {\n throw new Error(`Encrypted chunk exceeds chunkSize. Adjust chunkSize.`)\n }\n\n chunks.push({ ciphertext, iv })\n offset += maxPlaintextSize\n }\n\n return { chunks, secretKey }\n}\n"],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ import { EncryptedData } from "@towns-labs/proto";
2
+
3
+ //#region src/node/index.d.ts
4
+ declare function uint8ArrayToBase64(uint8Array: Uint8Array): string;
5
+ declare function base64ToUint8Array(base64: string): Uint8Array;
6
+ declare function deriveKeyAndIV(keyPhrase: string | Uint8Array): Promise<{
7
+ key: Uint8Array;
8
+ iv: Uint8Array;
9
+ }>;
10
+ declare function encryptAESGCM(data: Uint8Array, key?: Uint8Array, iv?: Uint8Array): Promise<{
11
+ ciphertext: Uint8Array;
12
+ iv: Uint8Array;
13
+ secretKey: Uint8Array;
14
+ }>;
15
+ declare function decryptAESGCM(data: Uint8Array | string, key: Uint8Array, iv: Uint8Array): Promise<Uint8Array>;
16
+ declare function decryptDerivedAESGCM(keyPhrase: string, encryptedData: EncryptedData): Promise<Uint8Array>;
17
+ declare function encryptChunkedAESGCM(data: Uint8Array, chunkSize: number): Promise<{
18
+ chunks: Array<{
19
+ ciphertext: Uint8Array;
20
+ iv: Uint8Array;
21
+ }>;
22
+ secretKey: Uint8Array;
23
+ }>;
24
+ //# sourceMappingURL=index.d.ts.map
25
+
26
+ //#endregion
27
+ export { base64ToUint8Array, decryptAESGCM, decryptDerivedAESGCM, deriveKeyAndIV, encryptAESGCM, encryptChunkedAESGCM, uint8ArrayToBase64 };
28
+ //# sourceMappingURL=index.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.cts","names":[],"sources":["../../src/node/index.ts"],"sourcesContent":[],"mappings":";;;iBAKgB,kBAAA,aAA+B;iBAI/B,kBAAA,kBAAoC;AAJpC,iBA+BM,cAAA,CA/ByB,SAAU,EAAA,MAAA,GAgCjC,UAhCiC,CAAA,EAiCtD,OAjCsD,CAAA;EAIzC,GAAA,EA6BE,UA7BF;EA2BM,EAAA,EAEY,UAFZ;CAAc,CAAA;AACZ,iBAmBF,aAAA,CAnBE,IAAA,EAoBd,UApBc,EAAA,GAAA,CAAA,EAqBd,UArBc,EAAA,EAAA,CAAA,EAsBf,UAtBe,CAAA,EAuBrB,OAvBqB,CAAA;EAAU,UAChB,EAsBO,UAtBP;EAAU,EAAA,EAsBa,UAtBP;EAAU,SAAzC,EAsB6D,UAtB7D;AAAO,CAAA,CAAA;AAkBY,iBAiCA,aAAA,CAjCa,IAAA,EAkCzB,UAlCyB,GAAA,MAAA,EAAA,GAAA,EAmC1B,UAnC0B,EAAA,EAAA,EAoC3B,UApC2B,CAAA,EAqChC,OArCgC,CAqCxB,UArCwB,CAAA;AAAA,iBA+Eb,oBAAA,CA/Ea,SAAA,EAAA,MAAA,EAAA,aAAA,EAiFhB,aAjFgB,CAAA,EAkFhC,OAlFgC,CAkFxB,UAlFwB,CAAA;AACzB,iBA0FY,oBAAA,CA1FZ,IAAA,EA2FA,UA3FA,EAAA,SAAA,EAAA,MAAA,CAAA,EA6FP,OA7FO,CAAA;EAAU,MACV,EA6FE,KA7FF,CAAA;IACD,UAAA,EA4FuB,UA5FvB;IACgB,EAAA,EA2FuB,UA3FvB;EAAU,CAAA,CAAA;EAAgB,SAAa,EA4FjD,UA5FiD;CAAU,CAAA;AAAhE"}
@@ -0,0 +1,28 @@
1
+ import { EncryptedData } from "@towns-labs/proto";
2
+
3
+ //#region src/node/index.d.ts
4
+ declare function uint8ArrayToBase64(uint8Array: Uint8Array): string;
5
+ declare function base64ToUint8Array(base64: string): Uint8Array;
6
+ declare function deriveKeyAndIV(keyPhrase: string | Uint8Array): Promise<{
7
+ key: Uint8Array;
8
+ iv: Uint8Array;
9
+ }>;
10
+ declare function encryptAESGCM(data: Uint8Array, key?: Uint8Array, iv?: Uint8Array): Promise<{
11
+ ciphertext: Uint8Array;
12
+ iv: Uint8Array;
13
+ secretKey: Uint8Array;
14
+ }>;
15
+ declare function decryptAESGCM(data: Uint8Array | string, key: Uint8Array, iv: Uint8Array): Promise<Uint8Array>;
16
+ declare function decryptDerivedAESGCM(keyPhrase: string, encryptedData: EncryptedData): Promise<Uint8Array>;
17
+ declare function encryptChunkedAESGCM(data: Uint8Array, chunkSize: number): Promise<{
18
+ chunks: Array<{
19
+ ciphertext: Uint8Array;
20
+ iv: Uint8Array;
21
+ }>;
22
+ secretKey: Uint8Array;
23
+ }>;
24
+ //# sourceMappingURL=index.d.ts.map
25
+
26
+ //#endregion
27
+ export { base64ToUint8Array, decryptAESGCM, decryptDerivedAESGCM, deriveKeyAndIV, encryptAESGCM, encryptChunkedAESGCM, uint8ArrayToBase64 };
28
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/node/index.ts"],"sourcesContent":[],"mappings":";;;iBAKgB,kBAAA,aAA+B;iBAI/B,kBAAA,kBAAoC;AAJpC,iBA+BM,cAAA,CA/ByB,SAAU,EAAA,MAAA,GAgCjC,UAhCiC,CAAA,EAiCtD,OAjCsD,CAAA;EAIzC,GAAA,EA6BE,UA7BF;EA2BM,EAAA,EAEY,UAFZ;CAAc,CAAA;AACZ,iBAmBF,aAAA,CAnBE,IAAA,EAoBd,UApBc,EAAA,GAAA,CAAA,EAqBd,UArBc,EAAA,EAAA,CAAA,EAsBf,UAtBe,CAAA,EAuBrB,OAvBqB,CAAA;EAAU,UAChB,EAsBO,UAtBP;EAAU,EAAA,EAsBa,UAtBP;EAAU,SAAzC,EAsB6D,UAtB7D;AAAO,CAAA,CAAA;AAkBY,iBAiCA,aAAA,CAjCa,IAAA,EAkCzB,UAlCyB,GAAA,MAAA,EAAA,GAAA,EAmC1B,UAnC0B,EAAA,EAAA,EAoC3B,UApC2B,CAAA,EAqChC,OArCgC,CAqCxB,UArCwB,CAAA;AAAA,iBA+Eb,oBAAA,CA/Ea,SAAA,EAAA,MAAA,EAAA,aAAA,EAiFhB,aAjFgB,CAAA,EAkFhC,OAlFgC,CAkFxB,UAlFwB,CAAA;AACzB,iBA0FY,oBAAA,CA1FZ,IAAA,EA2FA,UA3FA,EAAA,SAAA,EAAA,MAAA,CAAA,EA6FP,OA7FO,CAAA;EAAU,MACV,EA6FE,KA7FF,CAAA;IACD,UAAA,EA4FuB,UA5FvB;IACgB,EAAA,EA2FuB,UA3FvB;EAAU,CAAA,CAAA;EAAgB,SAAa,EA4FjD,UA5FiD;CAAU,CAAA;AAAhE"}
@@ -0,0 +1,105 @@
1
+ import { throwWithCode } from "@towns-labs/utils";
2
+ import { Err } from "@towns-labs/proto";
3
+ import { AES_GCM_DERIVED_ALGORITHM } from "@towns-labs/encryption";
4
+ import crypto from "node:crypto";
5
+
6
+ //#region src/node/index.ts
7
+ function uint8ArrayToBase64(uint8Array) {
8
+ return Buffer.from(uint8Array).toString("base64");
9
+ }
10
+ function base64ToUint8Array(base64) {
11
+ const buffer = Buffer.from(base64, "base64");
12
+ return new Uint8Array(buffer);
13
+ }
14
+ function bufferToUint8Array(buffer) {
15
+ return new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength);
16
+ }
17
+ function uint8ArrayToBuffer(uint8Array) {
18
+ return Buffer.from(uint8Array.buffer, uint8Array.byteOffset, uint8Array.byteLength);
19
+ }
20
+ async function getExtendedKeyMaterial(seedBuffer, length) {
21
+ const hash = crypto.createHash("sha256");
22
+ hash.update(uint8ArrayToBuffer(seedBuffer));
23
+ let keyMaterial = bufferToUint8Array(hash.digest());
24
+ while (keyMaterial.length < length) {
25
+ const newHash = crypto.createHash("sha256");
26
+ newHash.update(uint8ArrayToBuffer(keyMaterial));
27
+ keyMaterial = new Uint8Array([...keyMaterial, ...bufferToUint8Array(newHash.digest())]);
28
+ }
29
+ return keyMaterial.slice(0, length);
30
+ }
31
+ async function deriveKeyAndIV(keyPhrase) {
32
+ let keyBuffer;
33
+ if (typeof keyPhrase === "string") {
34
+ const encoder = new TextEncoder();
35
+ keyBuffer = encoder.encode(keyPhrase);
36
+ } else keyBuffer = keyPhrase;
37
+ const keyMaterial = await getExtendedKeyMaterial(keyBuffer, 44);
38
+ const key = keyMaterial.slice(0, 32);
39
+ const iv = keyMaterial.slice(32, 44);
40
+ return {
41
+ key,
42
+ iv
43
+ };
44
+ }
45
+ async function encryptAESGCM(data, key, iv) {
46
+ if (!data || data.length === 0) throw new Error("Cannot encrypt undefined or empty data");
47
+ if (!key) key = crypto.randomBytes(32);
48
+ else if (key.length !== 32) throw new Error("Invalid key length. AES-256-GCM requires a 32-byte key.");
49
+ if (!iv) iv = crypto.randomBytes(12);
50
+ else if (iv.length !== 12) throw new Error("Invalid IV length. AES-256-GCM requires a 12-byte IV.");
51
+ const cipher = crypto.createCipheriv("aes-256-gcm", uint8ArrayToBuffer(key), uint8ArrayToBuffer(iv));
52
+ const encrypted = Buffer.concat([cipher.update(uint8ArrayToBuffer(data)), cipher.final()]);
53
+ const authTag = cipher.getAuthTag();
54
+ const ciphertext = Buffer.concat([encrypted, authTag]);
55
+ return {
56
+ ciphertext: bufferToUint8Array(ciphertext),
57
+ iv,
58
+ secretKey: key
59
+ };
60
+ }
61
+ async function decryptAESGCM(data, key, iv) {
62
+ if (key.length !== 32) throw new Error("Invalid key length. AES-256-GCM requires a 32-byte key.");
63
+ if (iv.length !== 12) throw new Error("Invalid IV length. AES-256-GCM requires a 12-byte IV.");
64
+ let dataBuffer;
65
+ if (typeof data === "string") dataBuffer = Buffer.from(data, "base64");
66
+ else dataBuffer = data;
67
+ const encryptedBuffer = Buffer.from(dataBuffer.buffer, dataBuffer.byteOffset, dataBuffer.byteLength);
68
+ const authTag = new Uint8Array(encryptedBuffer.buffer.slice(encryptedBuffer.byteOffset + encryptedBuffer.length - 16, encryptedBuffer.byteOffset + encryptedBuffer.length));
69
+ const encryptedContent = new Uint8Array(encryptedBuffer.buffer.slice(encryptedBuffer.byteOffset, encryptedBuffer.byteOffset + encryptedBuffer.length - 16));
70
+ const decipher = crypto.createDecipheriv("aes-256-gcm", key, iv);
71
+ decipher.setAuthTag(authTag);
72
+ const decrypted = Buffer.concat([decipher.update(encryptedContent), decipher.final()]);
73
+ return new Uint8Array(decrypted.buffer, decrypted.byteOffset, decrypted.byteLength);
74
+ }
75
+ async function decryptDerivedAESGCM(keyPhrase, encryptedData) {
76
+ if (encryptedData.algorithm !== AES_GCM_DERIVED_ALGORITHM) throwWithCode(`${encryptedData.algorithm}" algorithm not implemented`, Err.UNIMPLEMENTED);
77
+ const { key, iv } = await deriveKeyAndIV(keyPhrase);
78
+ const ciphertext = base64ToUint8Array(encryptedData.ciphertext);
79
+ return decryptAESGCM(ciphertext, key, iv);
80
+ }
81
+ async function encryptChunkedAESGCM(data, chunkSize) {
82
+ if (!data || data.length === 0) throw new Error("Cannot encrypt undefined or empty data");
83
+ const secretKey = crypto.getRandomValues(new Uint8Array(32));
84
+ const maxPlaintextSize = chunkSize - 16;
85
+ const chunks = [];
86
+ let offset = 0;
87
+ while (offset < data.length) {
88
+ const dataChunk = data.slice(offset, offset + maxPlaintextSize);
89
+ const { ciphertext, iv } = await encryptAESGCM(dataChunk, secretKey);
90
+ if (ciphertext.byteLength > chunkSize) throw new Error(`Encrypted chunk exceeds chunkSize. Adjust chunkSize.`);
91
+ chunks.push({
92
+ ciphertext,
93
+ iv
94
+ });
95
+ offset += maxPlaintextSize;
96
+ }
97
+ return {
98
+ chunks,
99
+ secretKey
100
+ };
101
+ }
102
+
103
+ //#endregion
104
+ export { base64ToUint8Array, decryptAESGCM, decryptDerivedAESGCM, deriveKeyAndIV, encryptAESGCM, encryptChunkedAESGCM, uint8ArrayToBase64 };
105
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":["uint8Array: Uint8Array","base64: string","buffer: Buffer","seedBuffer: Uint8Array","length: number","keyPhrase: string | Uint8Array","keyBuffer: Uint8Array","data: Uint8Array","key?: Uint8Array","iv?: Uint8Array","data: Uint8Array | string","key: Uint8Array","iv: Uint8Array","dataBuffer: Uint8Array","keyPhrase: string","encryptedData: EncryptedData","chunkSize: number","chunks: Array<{ ciphertext: Uint8Array; iv: Uint8Array }>"],"sources":["../../src/node/index.ts"],"sourcesContent":["import { throwWithCode } from '@towns-labs/utils'\nimport { EncryptedData, Err } from '@towns-labs/proto'\nimport { AES_GCM_DERIVED_ALGORITHM } from '@towns-labs/encryption'\nimport crypto from 'node:crypto'\n\nexport function uint8ArrayToBase64(uint8Array: Uint8Array): string {\n return Buffer.from(uint8Array).toString('base64')\n}\n\nexport function base64ToUint8Array(base64: string): Uint8Array {\n const buffer = Buffer.from(base64, 'base64')\n return new Uint8Array(buffer)\n}\n\nfunction bufferToUint8Array(buffer: Buffer): Uint8Array {\n return new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength)\n}\n\nfunction uint8ArrayToBuffer(uint8Array: Uint8Array): Buffer {\n return Buffer.from(uint8Array.buffer, uint8Array.byteOffset, uint8Array.byteLength)\n}\n\nasync function getExtendedKeyMaterial(seedBuffer: Uint8Array, length: number): Promise<Uint8Array> {\n const hash = crypto.createHash('sha256')\n hash.update(uint8ArrayToBuffer(seedBuffer))\n let keyMaterial = bufferToUint8Array(hash.digest())\n\n while (keyMaterial.length < length) {\n const newHash = crypto.createHash('sha256')\n newHash.update(uint8ArrayToBuffer(keyMaterial))\n keyMaterial = new Uint8Array([...keyMaterial, ...bufferToUint8Array(newHash.digest())])\n }\n\n return keyMaterial.slice(0, length)\n}\n\nexport async function deriveKeyAndIV(\n keyPhrase: string | Uint8Array,\n): Promise<{ key: Uint8Array; iv: Uint8Array }> {\n let keyBuffer: Uint8Array\n\n if (typeof keyPhrase === 'string') {\n const encoder = new TextEncoder()\n keyBuffer = encoder.encode(keyPhrase)\n } else {\n keyBuffer = keyPhrase\n }\n\n const keyMaterial = await getExtendedKeyMaterial(keyBuffer, 32 + 12) // 32 bytes for key, 12 bytes for IV\n\n const key = keyMaterial.slice(0, 32) // AES-256 key\n const iv = keyMaterial.slice(32, 32 + 12) // AES-GCM IV\n\n return { key, iv }\n}\n\nexport async function encryptAESGCM(\n data: Uint8Array,\n key?: Uint8Array,\n iv?: Uint8Array,\n): Promise<{ ciphertext: Uint8Array; iv: Uint8Array; secretKey: Uint8Array }> {\n if (!data || data.length === 0) {\n throw new Error('Cannot encrypt undefined or empty data')\n }\n\n if (!key) {\n key = crypto.randomBytes(32)\n } else if (key.length !== 32) {\n throw new Error('Invalid key length. AES-256-GCM requires a 32-byte key.')\n }\n\n if (!iv) {\n iv = crypto.randomBytes(12)\n } else if (iv.length !== 12) {\n throw new Error('Invalid IV length. AES-256-GCM requires a 12-byte IV.')\n }\n\n const cipher = crypto.createCipheriv(\n 'aes-256-gcm',\n uint8ArrayToBuffer(key),\n uint8ArrayToBuffer(iv),\n )\n const encrypted = Buffer.concat([cipher.update(uint8ArrayToBuffer(data)), cipher.final()])\n const authTag = cipher.getAuthTag()\n const ciphertext = Buffer.concat([encrypted, authTag])\n\n return { ciphertext: bufferToUint8Array(ciphertext), iv, secretKey: key }\n}\n\nexport async function decryptAESGCM(\n data: Uint8Array | string,\n key: Uint8Array,\n iv: Uint8Array,\n): Promise<Uint8Array> {\n if (key.length !== 32) {\n throw new Error('Invalid key length. AES-256-GCM requires a 32-byte key.')\n }\n\n if (iv.length !== 12) {\n throw new Error('Invalid IV length. AES-256-GCM requires a 12-byte IV.')\n }\n\n // Convert data to Uint8Array if it is a string\n let dataBuffer: Uint8Array\n if (typeof data === 'string') {\n dataBuffer = Buffer.from(data, 'base64')\n } else {\n dataBuffer = data\n }\n\n const encryptedBuffer = Buffer.from(\n dataBuffer.buffer,\n dataBuffer.byteOffset,\n dataBuffer.byteLength,\n )\n const authTag = new Uint8Array(\n encryptedBuffer.buffer.slice(\n encryptedBuffer.byteOffset + encryptedBuffer.length - 16,\n encryptedBuffer.byteOffset + encryptedBuffer.length,\n ),\n )\n const encryptedContent = new Uint8Array(\n encryptedBuffer.buffer.slice(\n encryptedBuffer.byteOffset,\n encryptedBuffer.byteOffset + encryptedBuffer.length - 16,\n ),\n )\n\n const decipher = crypto.createDecipheriv('aes-256-gcm', key, iv)\n decipher.setAuthTag(authTag)\n\n const decrypted = Buffer.concat([decipher.update(encryptedContent), decipher.final()])\n return new Uint8Array(decrypted.buffer, decrypted.byteOffset, decrypted.byteLength)\n}\n\nexport async function decryptDerivedAESGCM(\n keyPhrase: string,\n encryptedData: EncryptedData,\n): Promise<Uint8Array> {\n if (encryptedData.algorithm !== AES_GCM_DERIVED_ALGORITHM) {\n throwWithCode(`${encryptedData.algorithm}\" algorithm not implemented`, Err.UNIMPLEMENTED)\n }\n const { key, iv } = await deriveKeyAndIV(keyPhrase)\n const ciphertext = base64ToUint8Array(encryptedData.ciphertext)\n return decryptAESGCM(ciphertext, key, iv)\n}\n\nexport async function encryptChunkedAESGCM(\n data: Uint8Array,\n chunkSize: number,\n): Promise<{\n chunks: Array<{ ciphertext: Uint8Array; iv: Uint8Array }>\n secretKey: Uint8Array\n}> {\n if (!data || data.length === 0) {\n throw new Error('Cannot encrypt undefined or empty data')\n }\n const secretKey = crypto.getRandomValues(new Uint8Array(32))\n // Adjust chunk size to account for AES-GCM overhead (16-byte auth tag)\n const maxPlaintextSize = chunkSize - 16\n const chunks: Array<{ ciphertext: Uint8Array; iv: Uint8Array }> = []\n\n let offset = 0\n while (offset < data.length) {\n const dataChunk = data.slice(offset, offset + maxPlaintextSize)\n const { ciphertext, iv } = await encryptAESGCM(dataChunk, secretKey)\n\n if (ciphertext.byteLength > chunkSize) {\n throw new Error(`Encrypted chunk exceeds chunkSize. Adjust chunkSize.`)\n }\n\n chunks.push({ ciphertext, iv })\n offset += maxPlaintextSize\n }\n\n return { chunks, secretKey }\n}\n"],"mappings":"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"}
@@ -0,0 +1,111 @@
1
+ const require_chunk = require('../chunk-CUT6urMc.cjs');
2
+ const __towns_labs_utils = require_chunk.__toESM(require("@towns-labs/utils"));
3
+ const __towns_labs_proto = require_chunk.__toESM(require("@towns-labs/proto"));
4
+ const __towns_labs_encryption = require_chunk.__toESM(require("@towns-labs/encryption"));
5
+
6
+ //#region src/web/index.ts
7
+ function uint8ArrayToBase64(uint8Array) {
8
+ const binary = Array.from(uint8Array, (byte) => String.fromCharCode(byte)).join("");
9
+ return btoa(binary);
10
+ }
11
+ function base64ToUint8Array(base64) {
12
+ const binary = atob(base64);
13
+ return new Uint8Array(Array.from(binary, (char) => char.charCodeAt(0)));
14
+ }
15
+ async function getExtendedKeyMaterial(seedBuffer, length) {
16
+ let keyMaterial = new Uint8Array(await crypto.subtle.digest("SHA-256", seedBuffer));
17
+ while (keyMaterial.length < length) {
18
+ const newHash = new Uint8Array(await crypto.subtle.digest("SHA-256", keyMaterial));
19
+ const combined = new Uint8Array(keyMaterial.length + newHash.length);
20
+ combined.set(keyMaterial);
21
+ combined.set(newHash, keyMaterial.length);
22
+ keyMaterial = combined;
23
+ }
24
+ return keyMaterial.slice(0, length);
25
+ }
26
+ async function deriveKeyAndIV(keyPhrase) {
27
+ let keyBuffer;
28
+ if (typeof keyPhrase === "string") {
29
+ const encoder = new TextEncoder();
30
+ keyBuffer = encoder.encode(keyPhrase);
31
+ } else keyBuffer = keyPhrase;
32
+ const keyMaterial = await getExtendedKeyMaterial(keyBuffer, 44);
33
+ const key = keyMaterial.slice(0, 32);
34
+ const iv = keyMaterial.slice(32, 44);
35
+ return {
36
+ key,
37
+ iv
38
+ };
39
+ }
40
+ async function encryptAESGCM(data, key, iv) {
41
+ if (!data || data.length === 0) throw new Error("Cannot encrypt undefined or empty data");
42
+ if (!key) {
43
+ key = new Uint8Array(32);
44
+ crypto.getRandomValues(key);
45
+ } else if (key.length !== 32) throw new Error("Invalid key length. AES-256-GCM requires a 32-byte key.");
46
+ if (!iv) {
47
+ iv = new Uint8Array(12);
48
+ crypto.getRandomValues(iv);
49
+ } else if (iv.length !== 12) throw new Error("Invalid IV length. AES-256-GCM requires a 12-byte IV.");
50
+ const cryptoKey = await crypto.subtle.importKey("raw", key, { name: "AES-GCM" }, false, ["encrypt"]);
51
+ const encryptedBuffer = await crypto.subtle.encrypt({
52
+ name: "AES-GCM",
53
+ iv
54
+ }, cryptoKey, data);
55
+ const ciphertext = new Uint8Array(encryptedBuffer);
56
+ return {
57
+ ciphertext,
58
+ iv,
59
+ secretKey: key
60
+ };
61
+ }
62
+ async function decryptAESGCM(data, key, iv) {
63
+ if (key.length !== 32) throw new Error("Invalid key length. AES-256-GCM requires a 32-byte key.");
64
+ if (iv.length !== 12) throw new Error("Invalid IV length. AES-256-GCM requires a 12-byte IV.");
65
+ let dataBuffer;
66
+ if (typeof data === "string") dataBuffer = base64ToUint8Array(data);
67
+ else dataBuffer = data;
68
+ const cryptoKey = await crypto.subtle.importKey("raw", key, { name: "AES-GCM" }, false, ["decrypt"]);
69
+ const decryptedBuffer = await crypto.subtle.decrypt({
70
+ name: "AES-GCM",
71
+ iv
72
+ }, cryptoKey, dataBuffer);
73
+ return new Uint8Array(decryptedBuffer);
74
+ }
75
+ async function decryptDerivedAESGCM(keyPhrase, encryptedData) {
76
+ if (encryptedData.algorithm !== __towns_labs_encryption.AES_GCM_DERIVED_ALGORITHM) (0, __towns_labs_utils.throwWithCode)(`${encryptedData.algorithm}" algorithm not implemented`, __towns_labs_proto.Err.UNIMPLEMENTED);
77
+ const { key, iv } = await deriveKeyAndIV(keyPhrase);
78
+ const ciphertext = base64ToUint8Array(encryptedData.ciphertext);
79
+ return decryptAESGCM(ciphertext, key, iv);
80
+ }
81
+ async function encryptChunkedAESGCM(data, chunkSize) {
82
+ if (!data || data.length === 0) throw new Error("Cannot encrypt undefined or empty data");
83
+ const secretKey = crypto.getRandomValues(new Uint8Array(32));
84
+ const maxPlaintextSize = chunkSize - 16;
85
+ const chunks = [];
86
+ let offset = 0;
87
+ while (offset < data.length) {
88
+ const dataChunk = data.slice(offset, offset + maxPlaintextSize);
89
+ const { ciphertext, iv } = await encryptAESGCM(dataChunk, secretKey);
90
+ if (ciphertext.byteLength > chunkSize) throw new Error(`Encrypted chunk exceeds chunkSize. Adjust chunkSize.`);
91
+ chunks.push({
92
+ ciphertext,
93
+ iv
94
+ });
95
+ offset += maxPlaintextSize;
96
+ }
97
+ return {
98
+ chunks,
99
+ secretKey
100
+ };
101
+ }
102
+
103
+ //#endregion
104
+ exports.base64ToUint8Array = base64ToUint8Array;
105
+ exports.decryptAESGCM = decryptAESGCM;
106
+ exports.decryptDerivedAESGCM = decryptDerivedAESGCM;
107
+ exports.deriveKeyAndIV = deriveKeyAndIV;
108
+ exports.encryptAESGCM = encryptAESGCM;
109
+ exports.encryptChunkedAESGCM = encryptChunkedAESGCM;
110
+ exports.uint8ArrayToBase64 = uint8ArrayToBase64;
111
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs","names":["uint8Array: Uint8Array","base64: string","seedBuffer: Uint8Array","length: number","keyPhrase: string | Uint8Array","keyBuffer: Uint8Array","data: Uint8Array","key?: Uint8Array","iv?: Uint8Array","data: Uint8Array | string","key: Uint8Array","iv: Uint8Array","dataBuffer: Uint8Array","keyPhrase: string","encryptedData: EncryptedData","AES_GCM_DERIVED_ALGORITHM","Err","chunkSize: number","chunks: Array<{ ciphertext: Uint8Array; iv: Uint8Array }>"],"sources":["../../src/web/index.ts"],"sourcesContent":["import { throwWithCode } from '@towns-labs/utils'\nimport { EncryptedData, Err } from '@towns-labs/proto'\nimport { AES_GCM_DERIVED_ALGORITHM } from '@towns-labs/encryption'\n\nexport function uint8ArrayToBase64(uint8Array: Uint8Array): string {\n const binary = Array.from(uint8Array, (byte) => String.fromCharCode(byte)).join('')\n return btoa(binary)\n}\n\nexport function base64ToUint8Array(base64: string): Uint8Array {\n const binary = atob(base64)\n return new Uint8Array(Array.from(binary, (char) => char.charCodeAt(0)))\n}\n\nasync function getExtendedKeyMaterial(seedBuffer: Uint8Array, length: number): Promise<Uint8Array> {\n let keyMaterial = new Uint8Array(await crypto.subtle.digest('SHA-256', seedBuffer))\n\n while (keyMaterial.length < length) {\n const newHash = new Uint8Array(await crypto.subtle.digest('SHA-256', keyMaterial))\n const combined = new Uint8Array(keyMaterial.length + newHash.length)\n combined.set(keyMaterial)\n combined.set(newHash, keyMaterial.length)\n keyMaterial = combined\n }\n\n return keyMaterial.slice(0, length)\n}\n\nexport async function deriveKeyAndIV(\n keyPhrase: string | Uint8Array,\n): Promise<{ key: Uint8Array; iv: Uint8Array }> {\n let keyBuffer: Uint8Array\n\n if (typeof keyPhrase === 'string') {\n const encoder = new TextEncoder()\n keyBuffer = encoder.encode(keyPhrase)\n } else {\n keyBuffer = keyPhrase\n }\n\n const keyMaterial = await getExtendedKeyMaterial(keyBuffer, 32 + 12) // 32 bytes for key, 12 bytes for IV\n\n const key = keyMaterial.slice(0, 32) // AES-256 key\n const iv = keyMaterial.slice(32, 32 + 12) // AES-GCM IV\n\n return { key, iv }\n}\n\nexport async function encryptAESGCM(\n data: Uint8Array,\n key?: Uint8Array,\n iv?: Uint8Array,\n): Promise<{ ciphertext: Uint8Array; iv: Uint8Array; secretKey: Uint8Array }> {\n if (!data || data.length === 0) {\n throw new Error('Cannot encrypt undefined or empty data')\n }\n\n if (!key) {\n key = new Uint8Array(32)\n crypto.getRandomValues(key)\n } else if (key.length !== 32) {\n throw new Error('Invalid key length. AES-256-GCM requires a 32-byte key.')\n }\n\n if (!iv) {\n iv = new Uint8Array(12)\n crypto.getRandomValues(iv)\n } else if (iv.length !== 12) {\n throw new Error('Invalid IV length. AES-256-GCM requires a 12-byte IV.')\n }\n\n const cryptoKey = await crypto.subtle.importKey('raw', key, { name: 'AES-GCM' }, false, [\n 'encrypt',\n ])\n\n const encryptedBuffer = await crypto.subtle.encrypt(\n {\n name: 'AES-GCM',\n iv: iv,\n },\n cryptoKey,\n data,\n )\n\n const ciphertext = new Uint8Array(encryptedBuffer)\n\n return { ciphertext, iv, secretKey: key }\n}\n\nexport async function decryptAESGCM(\n data: Uint8Array | string,\n key: Uint8Array,\n iv: Uint8Array,\n): Promise<Uint8Array> {\n if (key.length !== 32) {\n throw new Error('Invalid key length. AES-256-GCM requires a 32-byte key.')\n }\n\n if (iv.length !== 12) {\n throw new Error('Invalid IV length. AES-256-GCM requires a 12-byte IV.')\n }\n\n let dataBuffer: Uint8Array\n if (typeof data === 'string') {\n dataBuffer = base64ToUint8Array(data)\n } else {\n dataBuffer = data\n }\n\n const cryptoKey = await crypto.subtle.importKey('raw', key, { name: 'AES-GCM' }, false, [\n 'decrypt',\n ])\n\n const decryptedBuffer = await crypto.subtle.decrypt(\n {\n name: 'AES-GCM',\n iv: iv,\n },\n cryptoKey,\n dataBuffer,\n )\n\n return new Uint8Array(decryptedBuffer)\n}\n\nexport async function decryptDerivedAESGCM(\n keyPhrase: string,\n encryptedData: EncryptedData,\n): Promise<Uint8Array> {\n if (encryptedData.algorithm !== AES_GCM_DERIVED_ALGORITHM) {\n throwWithCode(`${encryptedData.algorithm}\" algorithm not implemented`, Err.UNIMPLEMENTED)\n }\n const { key, iv } = await deriveKeyAndIV(keyPhrase)\n const ciphertext = base64ToUint8Array(encryptedData.ciphertext)\n return decryptAESGCM(ciphertext, key, iv)\n}\n\nexport async function encryptChunkedAESGCM(\n data: Uint8Array,\n chunkSize: number,\n): Promise<{\n chunks: Array<{ ciphertext: Uint8Array; iv: Uint8Array }>\n secretKey: Uint8Array\n}> {\n if (!data || data.length === 0) {\n throw new Error('Cannot encrypt undefined or empty data')\n }\n const secretKey = crypto.getRandomValues(new Uint8Array(32))\n // Adjust chunk size to account for AES-GCM overhead (16-byte auth tag)\n const maxPlaintextSize = chunkSize - 16\n const chunks: Array<{ ciphertext: Uint8Array; iv: Uint8Array }> = []\n\n let offset = 0\n while (offset < data.length) {\n const dataChunk = data.slice(offset, offset + maxPlaintextSize)\n const { ciphertext, iv } = await encryptAESGCM(dataChunk, secretKey)\n\n if (ciphertext.byteLength > chunkSize) {\n throw new Error(`Encrypted chunk exceeds chunkSize. Adjust chunkSize.`)\n }\n\n chunks.push({ ciphertext, iv })\n offset += maxPlaintextSize\n }\n\n return { chunks, secretKey }\n}\n"],"mappings":";;;;;;AAIA,SAAgB,mBAAmBA,YAAgC;CAC/D,MAAM,SAAS,MAAM,KAAK,YAAY,CAAC,SAAS,OAAO,aAAa,KAAK,CAAC,CAAC,KAAK,GAAG;AACnF,QAAO,KAAK,OAAO;AACtB;AAED,SAAgB,mBAAmBC,QAA4B;CAC3D,MAAM,SAAS,KAAK,OAAO;AAC3B,QAAO,IAAI,WAAW,MAAM,KAAK,QAAQ,CAAC,SAAS,KAAK,WAAW,EAAE,CAAC;AACzE;AAED,eAAe,uBAAuBC,YAAwBC,QAAqC;CAC/F,IAAI,cAAc,IAAI,WAAW,MAAM,OAAO,OAAO,OAAO,WAAW,WAAW;AAElF,QAAO,YAAY,SAAS,QAAQ;EAChC,MAAM,UAAU,IAAI,WAAW,MAAM,OAAO,OAAO,OAAO,WAAW,YAAY;EACjF,MAAM,WAAW,IAAI,WAAW,YAAY,SAAS,QAAQ;AAC7D,WAAS,IAAI,YAAY;AACzB,WAAS,IAAI,SAAS,YAAY,OAAO;AACzC,gBAAc;CACjB;AAED,QAAO,YAAY,MAAM,GAAG,OAAO;AACtC;AAED,eAAsB,eAClBC,WAC4C;CAC5C,IAAIC;AAEJ,YAAW,cAAc,UAAU;EAC/B,MAAM,UAAU,IAAI;AACpB,cAAY,QAAQ,OAAO,UAAU;CACxC,MACG,aAAY;CAGhB,MAAM,cAAc,MAAM,uBAAuB,WAAW,GAAQ;CAEpE,MAAM,MAAM,YAAY,MAAM,GAAG,GAAG;CACpC,MAAM,KAAK,YAAY,MAAM,IAAI,GAAQ;AAEzC,QAAO;EAAE;EAAK;CAAI;AACrB;AAED,eAAsB,cAClBC,MACAC,KACAC,IAC0E;AAC1E,MAAK,QAAQ,KAAK,WAAW,EACzB,OAAM,IAAI,MAAM;AAGpB,MAAK,KAAK;AACN,QAAM,IAAI,WAAW;AACrB,SAAO,gBAAgB,IAAI;CAC9B,WAAU,IAAI,WAAW,GACtB,OAAM,IAAI,MAAM;AAGpB,MAAK,IAAI;AACL,OAAK,IAAI,WAAW;AACpB,SAAO,gBAAgB,GAAG;CAC7B,WAAU,GAAG,WAAW,GACrB,OAAM,IAAI,MAAM;CAGpB,MAAM,YAAY,MAAM,OAAO,OAAO,UAAU,OAAO,KAAK,EAAE,MAAM,UAAW,GAAE,OAAO,CACpF,SACH,EAAC;CAEF,MAAM,kBAAkB,MAAM,OAAO,OAAO,QACxC;EACI,MAAM;EACF;CACP,GACD,WACA,KACH;CAED,MAAM,aAAa,IAAI,WAAW;AAElC,QAAO;EAAE;EAAY;EAAI,WAAW;CAAK;AAC5C;AAED,eAAsB,cAClBC,MACAC,KACAC,IACmB;AACnB,KAAI,IAAI,WAAW,GACf,OAAM,IAAI,MAAM;AAGpB,KAAI,GAAG,WAAW,GACd,OAAM,IAAI,MAAM;CAGpB,IAAIC;AACJ,YAAW,SAAS,SAChB,cAAa,mBAAmB,KAAK;KAErC,cAAa;CAGjB,MAAM,YAAY,MAAM,OAAO,OAAO,UAAU,OAAO,KAAK,EAAE,MAAM,UAAW,GAAE,OAAO,CACpF,SACH,EAAC;CAEF,MAAM,kBAAkB,MAAM,OAAO,OAAO,QACxC;EACI,MAAM;EACF;CACP,GACD,WACA,WACH;AAED,QAAO,IAAI,WAAW;AACzB;AAED,eAAsB,qBAClBC,WACAC,eACmB;AACnB,KAAI,cAAc,cAAcC,kDAC5B,wCAAe,EAAE,cAAc,UAAU,8BAA8BC,uBAAI,cAAc;CAE7F,MAAM,EAAE,KAAK,IAAI,GAAG,MAAM,eAAe,UAAU;CACnD,MAAM,aAAa,mBAAmB,cAAc,WAAW;AAC/D,QAAO,cAAc,YAAY,KAAK,GAAG;AAC5C;AAED,eAAsB,qBAClBV,MACAW,WAID;AACC,MAAK,QAAQ,KAAK,WAAW,EACzB,OAAM,IAAI,MAAM;CAEpB,MAAM,YAAY,OAAO,gBAAgB,IAAI,WAAW,IAAI;CAE5D,MAAM,mBAAmB,YAAY;CACrC,MAAMC,SAA4D,CAAE;CAEpE,IAAI,SAAS;AACb,QAAO,SAAS,KAAK,QAAQ;EACzB,MAAM,YAAY,KAAK,MAAM,QAAQ,SAAS,iBAAiB;EAC/D,MAAM,EAAE,YAAY,IAAI,GAAG,MAAM,cAAc,WAAW,UAAU;AAEpE,MAAI,WAAW,aAAa,UACxB,OAAM,IAAI,OAAO;AAGrB,SAAO,KAAK;GAAE;GAAY;EAAI,EAAC;AAC/B,YAAU;CACb;AAED,QAAO;EAAE;EAAQ;CAAW;AAC/B"}
@@ -0,0 +1,28 @@
1
+ import { EncryptedData } from "@towns-labs/proto";
2
+
3
+ //#region src/web/index.d.ts
4
+ declare function uint8ArrayToBase64(uint8Array: Uint8Array): string;
5
+ declare function base64ToUint8Array(base64: string): Uint8Array;
6
+ declare function deriveKeyAndIV(keyPhrase: string | Uint8Array): Promise<{
7
+ key: Uint8Array;
8
+ iv: Uint8Array;
9
+ }>;
10
+ declare function encryptAESGCM(data: Uint8Array, key?: Uint8Array, iv?: Uint8Array): Promise<{
11
+ ciphertext: Uint8Array;
12
+ iv: Uint8Array;
13
+ secretKey: Uint8Array;
14
+ }>;
15
+ declare function decryptAESGCM(data: Uint8Array | string, key: Uint8Array, iv: Uint8Array): Promise<Uint8Array>;
16
+ declare function decryptDerivedAESGCM(keyPhrase: string, encryptedData: EncryptedData): Promise<Uint8Array>;
17
+ declare function encryptChunkedAESGCM(data: Uint8Array, chunkSize: number): Promise<{
18
+ chunks: Array<{
19
+ ciphertext: Uint8Array;
20
+ iv: Uint8Array;
21
+ }>;
22
+ secretKey: Uint8Array;
23
+ }>;
24
+ //# sourceMappingURL=index.d.ts.map
25
+
26
+ //#endregion
27
+ export { base64ToUint8Array, decryptAESGCM, decryptDerivedAESGCM, deriveKeyAndIV, encryptAESGCM, encryptChunkedAESGCM, uint8ArrayToBase64 };
28
+ //# sourceMappingURL=index.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.cts","names":[],"sources":["../../src/web/index.ts"],"sourcesContent":[],"mappings":";;;iBAIgB,kBAAA,aAA+B;iBAK/B,kBAAA,kBAAoC;AALpC,iBAwBM,cAAA,CAxByB,SAAU,EAAA,MAAA,GAyBjC,UAzBiC,CAAA,EA0BtD,OA1BsD,CAAA;EAKzC,GAAA,EAqBE,UArBF;EAmBM,EAAA,EAEY,UAFZ;CAAc,CAAA;AACZ,iBAmBF,aAAA,CAnBE,IAAA,EAoBd,UApBc,EAAA,GAAA,CAAA,EAqBd,UArBc,EAAA,EAAA,CAAA,EAsBf,UAtBe,CAAA,EAuBrB,OAvBqB,CAAA;EAAU,UAChB,EAsBO,UAtBP;EAAU,EAAA,EAsBa,UAtBP;EAAU,SAAzC,EAsB6D,UAtB7D;AAAO,CAAA,CAAA;AAkBY,iBAyCA,aAAA,CAzCa,IAAA,EA0CzB,UA1CyB,GAAA,MAAA,EAAA,GAAA,EA2C1B,UA3C0B,EAAA,EAAA,EA4C3B,UA5C2B,CAAA,EA6ChC,OA7CgC,CA6CxB,UA7CwB,CAAA;AAAA,iBA6Eb,oBAAA,CA7Ea,SAAA,EAAA,MAAA,EAAA,aAAA,EA+EhB,aA/EgB,CAAA,EAgFhC,OAhFgC,CAgFxB,UAhFwB,CAAA;AACzB,iBAwFY,oBAAA,CAxFZ,IAAA,EAyFA,UAzFA,EAAA,SAAA,EAAA,MAAA,CAAA,EA2FP,OA3FO,CAAA;EAAU,MACV,EA2FE,KA3FF,CAAA;IACD,UAAA,EA0FuB,UA1FvB;IACgB,EAAA,EAyFuB,UAzFvB;EAAU,CAAA,CAAA;EAAgB,SAAa,EA0FjD,UA1FiD;CAAU,CAAA;AAAhE"}
@@ -0,0 +1,28 @@
1
+ import { EncryptedData } from "@towns-labs/proto";
2
+
3
+ //#region src/web/index.d.ts
4
+ declare function uint8ArrayToBase64(uint8Array: Uint8Array): string;
5
+ declare function base64ToUint8Array(base64: string): Uint8Array;
6
+ declare function deriveKeyAndIV(keyPhrase: string | Uint8Array): Promise<{
7
+ key: Uint8Array;
8
+ iv: Uint8Array;
9
+ }>;
10
+ declare function encryptAESGCM(data: Uint8Array, key?: Uint8Array, iv?: Uint8Array): Promise<{
11
+ ciphertext: Uint8Array;
12
+ iv: Uint8Array;
13
+ secretKey: Uint8Array;
14
+ }>;
15
+ declare function decryptAESGCM(data: Uint8Array | string, key: Uint8Array, iv: Uint8Array): Promise<Uint8Array>;
16
+ declare function decryptDerivedAESGCM(keyPhrase: string, encryptedData: EncryptedData): Promise<Uint8Array>;
17
+ declare function encryptChunkedAESGCM(data: Uint8Array, chunkSize: number): Promise<{
18
+ chunks: Array<{
19
+ ciphertext: Uint8Array;
20
+ iv: Uint8Array;
21
+ }>;
22
+ secretKey: Uint8Array;
23
+ }>;
24
+ //# sourceMappingURL=index.d.ts.map
25
+
26
+ //#endregion
27
+ export { base64ToUint8Array, decryptAESGCM, decryptDerivedAESGCM, deriveKeyAndIV, encryptAESGCM, encryptChunkedAESGCM, uint8ArrayToBase64 };
28
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/web/index.ts"],"sourcesContent":[],"mappings":";;;iBAIgB,kBAAA,aAA+B;iBAK/B,kBAAA,kBAAoC;AALpC,iBAwBM,cAAA,CAxByB,SAAU,EAAA,MAAA,GAyBjC,UAzBiC,CAAA,EA0BtD,OA1BsD,CAAA;EAKzC,GAAA,EAqBE,UArBF;EAmBM,EAAA,EAEY,UAFZ;CAAc,CAAA;AACZ,iBAmBF,aAAA,CAnBE,IAAA,EAoBd,UApBc,EAAA,GAAA,CAAA,EAqBd,UArBc,EAAA,EAAA,CAAA,EAsBf,UAtBe,CAAA,EAuBrB,OAvBqB,CAAA;EAAU,UAChB,EAsBO,UAtBP;EAAU,EAAA,EAsBa,UAtBP;EAAU,SAAzC,EAsB6D,UAtB7D;AAAO,CAAA,CAAA;AAkBY,iBAyCA,aAAA,CAzCa,IAAA,EA0CzB,UA1CyB,GAAA,MAAA,EAAA,GAAA,EA2C1B,UA3C0B,EAAA,EAAA,EA4C3B,UA5C2B,CAAA,EA6ChC,OA7CgC,CA6CxB,UA7CwB,CAAA;AAAA,iBA6Eb,oBAAA,CA7Ea,SAAA,EAAA,MAAA,EAAA,aAAA,EA+EhB,aA/EgB,CAAA,EAgFhC,OAhFgC,CAgFxB,UAhFwB,CAAA;AACzB,iBAwFY,oBAAA,CAxFZ,IAAA,EAyFA,UAzFA,EAAA,SAAA,EAAA,MAAA,CAAA,EA2FP,OA3FO,CAAA;EAAU,MACV,EA2FE,KA3FF,CAAA;IACD,UAAA,EA0FuB,UA1FvB;IACgB,EAAA,EAyFuB,UAzFvB;EAAU,CAAA,CAAA;EAAgB,SAAa,EA0FjD,UA1FiD;CAAU,CAAA;AAAhE"}
@@ -0,0 +1,104 @@
1
+ import { throwWithCode } from "@towns-labs/utils";
2
+ import { Err } from "@towns-labs/proto";
3
+ import { AES_GCM_DERIVED_ALGORITHM } from "@towns-labs/encryption";
4
+
5
+ //#region src/web/index.ts
6
+ function uint8ArrayToBase64(uint8Array) {
7
+ const binary = Array.from(uint8Array, (byte) => String.fromCharCode(byte)).join("");
8
+ return btoa(binary);
9
+ }
10
+ function base64ToUint8Array(base64) {
11
+ const binary = atob(base64);
12
+ return new Uint8Array(Array.from(binary, (char) => char.charCodeAt(0)));
13
+ }
14
+ async function getExtendedKeyMaterial(seedBuffer, length) {
15
+ let keyMaterial = new Uint8Array(await crypto.subtle.digest("SHA-256", seedBuffer));
16
+ while (keyMaterial.length < length) {
17
+ const newHash = new Uint8Array(await crypto.subtle.digest("SHA-256", keyMaterial));
18
+ const combined = new Uint8Array(keyMaterial.length + newHash.length);
19
+ combined.set(keyMaterial);
20
+ combined.set(newHash, keyMaterial.length);
21
+ keyMaterial = combined;
22
+ }
23
+ return keyMaterial.slice(0, length);
24
+ }
25
+ async function deriveKeyAndIV(keyPhrase) {
26
+ let keyBuffer;
27
+ if (typeof keyPhrase === "string") {
28
+ const encoder = new TextEncoder();
29
+ keyBuffer = encoder.encode(keyPhrase);
30
+ } else keyBuffer = keyPhrase;
31
+ const keyMaterial = await getExtendedKeyMaterial(keyBuffer, 44);
32
+ const key = keyMaterial.slice(0, 32);
33
+ const iv = keyMaterial.slice(32, 44);
34
+ return {
35
+ key,
36
+ iv
37
+ };
38
+ }
39
+ async function encryptAESGCM(data, key, iv) {
40
+ if (!data || data.length === 0) throw new Error("Cannot encrypt undefined or empty data");
41
+ if (!key) {
42
+ key = new Uint8Array(32);
43
+ crypto.getRandomValues(key);
44
+ } else if (key.length !== 32) throw new Error("Invalid key length. AES-256-GCM requires a 32-byte key.");
45
+ if (!iv) {
46
+ iv = new Uint8Array(12);
47
+ crypto.getRandomValues(iv);
48
+ } else if (iv.length !== 12) throw new Error("Invalid IV length. AES-256-GCM requires a 12-byte IV.");
49
+ const cryptoKey = await crypto.subtle.importKey("raw", key, { name: "AES-GCM" }, false, ["encrypt"]);
50
+ const encryptedBuffer = await crypto.subtle.encrypt({
51
+ name: "AES-GCM",
52
+ iv
53
+ }, cryptoKey, data);
54
+ const ciphertext = new Uint8Array(encryptedBuffer);
55
+ return {
56
+ ciphertext,
57
+ iv,
58
+ secretKey: key
59
+ };
60
+ }
61
+ async function decryptAESGCM(data, key, iv) {
62
+ if (key.length !== 32) throw new Error("Invalid key length. AES-256-GCM requires a 32-byte key.");
63
+ if (iv.length !== 12) throw new Error("Invalid IV length. AES-256-GCM requires a 12-byte IV.");
64
+ let dataBuffer;
65
+ if (typeof data === "string") dataBuffer = base64ToUint8Array(data);
66
+ else dataBuffer = data;
67
+ const cryptoKey = await crypto.subtle.importKey("raw", key, { name: "AES-GCM" }, false, ["decrypt"]);
68
+ const decryptedBuffer = await crypto.subtle.decrypt({
69
+ name: "AES-GCM",
70
+ iv
71
+ }, cryptoKey, dataBuffer);
72
+ return new Uint8Array(decryptedBuffer);
73
+ }
74
+ async function decryptDerivedAESGCM(keyPhrase, encryptedData) {
75
+ if (encryptedData.algorithm !== AES_GCM_DERIVED_ALGORITHM) throwWithCode(`${encryptedData.algorithm}" algorithm not implemented`, Err.UNIMPLEMENTED);
76
+ const { key, iv } = await deriveKeyAndIV(keyPhrase);
77
+ const ciphertext = base64ToUint8Array(encryptedData.ciphertext);
78
+ return decryptAESGCM(ciphertext, key, iv);
79
+ }
80
+ async function encryptChunkedAESGCM(data, chunkSize) {
81
+ if (!data || data.length === 0) throw new Error("Cannot encrypt undefined or empty data");
82
+ const secretKey = crypto.getRandomValues(new Uint8Array(32));
83
+ const maxPlaintextSize = chunkSize - 16;
84
+ const chunks = [];
85
+ let offset = 0;
86
+ while (offset < data.length) {
87
+ const dataChunk = data.slice(offset, offset + maxPlaintextSize);
88
+ const { ciphertext, iv } = await encryptAESGCM(dataChunk, secretKey);
89
+ if (ciphertext.byteLength > chunkSize) throw new Error(`Encrypted chunk exceeds chunkSize. Adjust chunkSize.`);
90
+ chunks.push({
91
+ ciphertext,
92
+ iv
93
+ });
94
+ offset += maxPlaintextSize;
95
+ }
96
+ return {
97
+ chunks,
98
+ secretKey
99
+ };
100
+ }
101
+
102
+ //#endregion
103
+ export { base64ToUint8Array, decryptAESGCM, decryptDerivedAESGCM, deriveKeyAndIV, encryptAESGCM, encryptChunkedAESGCM, uint8ArrayToBase64 };
104
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":["uint8Array: Uint8Array","base64: string","seedBuffer: Uint8Array","length: number","keyPhrase: string | Uint8Array","keyBuffer: Uint8Array","data: Uint8Array","key?: Uint8Array","iv?: Uint8Array","data: Uint8Array | string","key: Uint8Array","iv: Uint8Array","dataBuffer: Uint8Array","keyPhrase: string","encryptedData: EncryptedData","chunkSize: number","chunks: Array<{ ciphertext: Uint8Array; iv: Uint8Array }>"],"sources":["../../src/web/index.ts"],"sourcesContent":["import { throwWithCode } from '@towns-labs/utils'\nimport { EncryptedData, Err } from '@towns-labs/proto'\nimport { AES_GCM_DERIVED_ALGORITHM } from '@towns-labs/encryption'\n\nexport function uint8ArrayToBase64(uint8Array: Uint8Array): string {\n const binary = Array.from(uint8Array, (byte) => String.fromCharCode(byte)).join('')\n return btoa(binary)\n}\n\nexport function base64ToUint8Array(base64: string): Uint8Array {\n const binary = atob(base64)\n return new Uint8Array(Array.from(binary, (char) => char.charCodeAt(0)))\n}\n\nasync function getExtendedKeyMaterial(seedBuffer: Uint8Array, length: number): Promise<Uint8Array> {\n let keyMaterial = new Uint8Array(await crypto.subtle.digest('SHA-256', seedBuffer))\n\n while (keyMaterial.length < length) {\n const newHash = new Uint8Array(await crypto.subtle.digest('SHA-256', keyMaterial))\n const combined = new Uint8Array(keyMaterial.length + newHash.length)\n combined.set(keyMaterial)\n combined.set(newHash, keyMaterial.length)\n keyMaterial = combined\n }\n\n return keyMaterial.slice(0, length)\n}\n\nexport async function deriveKeyAndIV(\n keyPhrase: string | Uint8Array,\n): Promise<{ key: Uint8Array; iv: Uint8Array }> {\n let keyBuffer: Uint8Array\n\n if (typeof keyPhrase === 'string') {\n const encoder = new TextEncoder()\n keyBuffer = encoder.encode(keyPhrase)\n } else {\n keyBuffer = keyPhrase\n }\n\n const keyMaterial = await getExtendedKeyMaterial(keyBuffer, 32 + 12) // 32 bytes for key, 12 bytes for IV\n\n const key = keyMaterial.slice(0, 32) // AES-256 key\n const iv = keyMaterial.slice(32, 32 + 12) // AES-GCM IV\n\n return { key, iv }\n}\n\nexport async function encryptAESGCM(\n data: Uint8Array,\n key?: Uint8Array,\n iv?: Uint8Array,\n): Promise<{ ciphertext: Uint8Array; iv: Uint8Array; secretKey: Uint8Array }> {\n if (!data || data.length === 0) {\n throw new Error('Cannot encrypt undefined or empty data')\n }\n\n if (!key) {\n key = new Uint8Array(32)\n crypto.getRandomValues(key)\n } else if (key.length !== 32) {\n throw new Error('Invalid key length. AES-256-GCM requires a 32-byte key.')\n }\n\n if (!iv) {\n iv = new Uint8Array(12)\n crypto.getRandomValues(iv)\n } else if (iv.length !== 12) {\n throw new Error('Invalid IV length. AES-256-GCM requires a 12-byte IV.')\n }\n\n const cryptoKey = await crypto.subtle.importKey('raw', key, { name: 'AES-GCM' }, false, [\n 'encrypt',\n ])\n\n const encryptedBuffer = await crypto.subtle.encrypt(\n {\n name: 'AES-GCM',\n iv: iv,\n },\n cryptoKey,\n data,\n )\n\n const ciphertext = new Uint8Array(encryptedBuffer)\n\n return { ciphertext, iv, secretKey: key }\n}\n\nexport async function decryptAESGCM(\n data: Uint8Array | string,\n key: Uint8Array,\n iv: Uint8Array,\n): Promise<Uint8Array> {\n if (key.length !== 32) {\n throw new Error('Invalid key length. AES-256-GCM requires a 32-byte key.')\n }\n\n if (iv.length !== 12) {\n throw new Error('Invalid IV length. AES-256-GCM requires a 12-byte IV.')\n }\n\n let dataBuffer: Uint8Array\n if (typeof data === 'string') {\n dataBuffer = base64ToUint8Array(data)\n } else {\n dataBuffer = data\n }\n\n const cryptoKey = await crypto.subtle.importKey('raw', key, { name: 'AES-GCM' }, false, [\n 'decrypt',\n ])\n\n const decryptedBuffer = await crypto.subtle.decrypt(\n {\n name: 'AES-GCM',\n iv: iv,\n },\n cryptoKey,\n dataBuffer,\n )\n\n return new Uint8Array(decryptedBuffer)\n}\n\nexport async function decryptDerivedAESGCM(\n keyPhrase: string,\n encryptedData: EncryptedData,\n): Promise<Uint8Array> {\n if (encryptedData.algorithm !== AES_GCM_DERIVED_ALGORITHM) {\n throwWithCode(`${encryptedData.algorithm}\" algorithm not implemented`, Err.UNIMPLEMENTED)\n }\n const { key, iv } = await deriveKeyAndIV(keyPhrase)\n const ciphertext = base64ToUint8Array(encryptedData.ciphertext)\n return decryptAESGCM(ciphertext, key, iv)\n}\n\nexport async function encryptChunkedAESGCM(\n data: Uint8Array,\n chunkSize: number,\n): Promise<{\n chunks: Array<{ ciphertext: Uint8Array; iv: Uint8Array }>\n secretKey: Uint8Array\n}> {\n if (!data || data.length === 0) {\n throw new Error('Cannot encrypt undefined or empty data')\n }\n const secretKey = crypto.getRandomValues(new Uint8Array(32))\n // Adjust chunk size to account for AES-GCM overhead (16-byte auth tag)\n const maxPlaintextSize = chunkSize - 16\n const chunks: Array<{ ciphertext: Uint8Array; iv: Uint8Array }> = []\n\n let offset = 0\n while (offset < data.length) {\n const dataChunk = data.slice(offset, offset + maxPlaintextSize)\n const { ciphertext, iv } = await encryptAESGCM(dataChunk, secretKey)\n\n if (ciphertext.byteLength > chunkSize) {\n throw new Error(`Encrypted chunk exceeds chunkSize. Adjust chunkSize.`)\n }\n\n chunks.push({ ciphertext, iv })\n offset += maxPlaintextSize\n }\n\n return { chunks, secretKey }\n}\n"],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "@towns-labs/sdk-crypto",
3
+ "version": "2.0.1",
4
+ "type": "module",
5
+ "main": "./dist/web/index.cjs",
6
+ "types": "./dist/web/index.d.ts",
7
+ "scripts": {
8
+ "build": "tsdown",
9
+ "cb": "bun run clean && bun run build",
10
+ "clean": "rm -rf dist",
11
+ "lint": "eslint --format unix ./src --max-warnings=0",
12
+ "lint:fix": "bun run lint --fix",
13
+ "test": "vitest",
14
+ "test:unit": "vitest run",
15
+ "watch": "tsdown --watch"
16
+ },
17
+ "dependencies": {
18
+ "@towns-labs/encryption": "^1.0.1",
19
+ "@towns-labs/proto": "^1.0.1",
20
+ "@towns-labs/utils": "^1.0.1"
21
+ },
22
+ "devDependencies": {
23
+ "@bufbuild/protobuf": "^2.9.0",
24
+ "@types/node": "^20.14.8",
25
+ "@typescript-eslint/eslint-plugin": "^8.29.0",
26
+ "@typescript-eslint/parser": "^8.29.0",
27
+ "eslint": "^8.57.1",
28
+ "eslint-import-resolver-typescript": "^4.3.2",
29
+ "eslint-plugin-import-x": "^4.10.2",
30
+ "eslint-plugin-tsdoc": "^0.3.0",
31
+ "happy-dom": "^19.0.2",
32
+ "tsdown": "^0.12.3",
33
+ "typescript": "~5.8.3",
34
+ "vitest": "^3.2.3"
35
+ },
36
+ "exports": {
37
+ ".": {
38
+ "types": "./dist/web/index.d.ts",
39
+ "node": "./dist/node/index.js",
40
+ "default": "./dist/web/index.js"
41
+ },
42
+ "./package.json": "./package.json"
43
+ },
44
+ "files": [
45
+ "/dist"
46
+ ],
47
+ "module": "./dist/web/index.js",
48
+ "publishConfig": {
49
+ "access": "public"
50
+ },
51
+ "sideEffects": false
52
+ }