@towns-protocol/sdk-crypto 0.0.370 → 0.0.372

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.
@@ -79,6 +79,27 @@ async function decryptDerivedAESGCM(keyPhrase, encryptedData) {
79
79
  const ciphertext = base64ToUint8Array(encryptedData.ciphertext);
80
80
  return decryptAESGCM(ciphertext, key, iv);
81
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
+ }
82
103
 
83
104
  //#endregion
84
105
  exports.base64ToUint8Array = base64ToUint8Array;
@@ -86,5 +107,6 @@ exports.decryptAESGCM = decryptAESGCM;
86
107
  exports.decryptDerivedAESGCM = decryptDerivedAESGCM;
87
108
  exports.deriveKeyAndIV = deriveKeyAndIV;
88
109
  exports.encryptAESGCM = encryptAESGCM;
110
+ exports.encryptChunkedAESGCM = encryptChunkedAESGCM;
89
111
  exports.uint8ArrayToBase64 = uint8ArrayToBase64;
90
112
  //# sourceMappingURL=index.cjs.map
@@ -1 +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"],"sources":["../../src/node/index.ts"],"sourcesContent":["import { throwWithCode } from '@towns-protocol/utils'\nimport { EncryptedData, Err } from '@towns-protocol/proto'\nimport { AES_GCM_DERIVED_ALGORITHM } from '@towns-protocol/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"],"mappings":"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"}
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-protocol/utils'\nimport { EncryptedData, Err } from '@towns-protocol/proto'\nimport { AES_GCM_DERIVED_ALGORITHM } from '@towns-protocol/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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"}
@@ -14,8 +14,15 @@ declare function encryptAESGCM(data: Uint8Array, key?: Uint8Array, iv?: Uint8Arr
14
14
  }>;
15
15
  declare function decryptAESGCM(data: Uint8Array | string, key: Uint8Array, iv: Uint8Array): Promise<Uint8Array>;
16
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
+ }>;
17
24
  //# sourceMappingURL=index.d.ts.map
18
25
 
19
26
  //#endregion
20
- export { base64ToUint8Array, decryptAESGCM, decryptDerivedAESGCM, deriveKeyAndIV, encryptAESGCM, uint8ArrayToBase64 };
27
+ export { base64ToUint8Array, decryptAESGCM, decryptDerivedAESGCM, deriveKeyAndIV, encryptAESGCM, encryptChunkedAESGCM, uint8ArrayToBase64 };
21
28
  //# sourceMappingURL=index.d.cts.map
@@ -1 +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"}
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"}
@@ -14,8 +14,15 @@ declare function encryptAESGCM(data: Uint8Array, key?: Uint8Array, iv?: Uint8Arr
14
14
  }>;
15
15
  declare function decryptAESGCM(data: Uint8Array | string, key: Uint8Array, iv: Uint8Array): Promise<Uint8Array>;
16
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
+ }>;
17
24
  //# sourceMappingURL=index.d.ts.map
18
25
 
19
26
  //#endregion
20
- export { base64ToUint8Array, decryptAESGCM, decryptDerivedAESGCM, deriveKeyAndIV, encryptAESGCM, uint8ArrayToBase64 };
27
+ export { base64ToUint8Array, decryptAESGCM, decryptDerivedAESGCM, deriveKeyAndIV, encryptAESGCM, encryptChunkedAESGCM, uint8ArrayToBase64 };
21
28
  //# sourceMappingURL=index.d.ts.map
@@ -1 +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"}
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"}
@@ -78,7 +78,28 @@ async function decryptDerivedAESGCM(keyPhrase, encryptedData) {
78
78
  const ciphertext = base64ToUint8Array(encryptedData.ciphertext);
79
79
  return decryptAESGCM(ciphertext, key, iv);
80
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
+ }
81
102
 
82
103
  //#endregion
83
- export { base64ToUint8Array, decryptAESGCM, decryptDerivedAESGCM, deriveKeyAndIV, encryptAESGCM, uint8ArrayToBase64 };
104
+ export { base64ToUint8Array, decryptAESGCM, decryptDerivedAESGCM, deriveKeyAndIV, encryptAESGCM, encryptChunkedAESGCM, uint8ArrayToBase64 };
84
105
  //# sourceMappingURL=index.js.map
@@ -1 +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"],"sources":["../../src/node/index.ts"],"sourcesContent":["import { throwWithCode } from '@towns-protocol/utils'\nimport { EncryptedData, Err } from '@towns-protocol/proto'\nimport { AES_GCM_DERIVED_ALGORITHM } from '@towns-protocol/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"],"mappings":"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"}
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-protocol/utils'\nimport { EncryptedData, Err } from '@towns-protocol/proto'\nimport { AES_GCM_DERIVED_ALGORITHM } from '@towns-protocol/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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"}
@@ -78,6 +78,27 @@ async function decryptDerivedAESGCM(keyPhrase, encryptedData) {
78
78
  const ciphertext = base64ToUint8Array(encryptedData.ciphertext);
79
79
  return decryptAESGCM(ciphertext, key, iv);
80
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
+ }
81
102
 
82
103
  //#endregion
83
104
  exports.base64ToUint8Array = base64ToUint8Array;
@@ -85,5 +106,6 @@ exports.decryptAESGCM = decryptAESGCM;
85
106
  exports.decryptDerivedAESGCM = decryptDerivedAESGCM;
86
107
  exports.deriveKeyAndIV = deriveKeyAndIV;
87
108
  exports.encryptAESGCM = encryptAESGCM;
109
+ exports.encryptChunkedAESGCM = encryptChunkedAESGCM;
88
110
  exports.uint8ArrayToBase64 = uint8ArrayToBase64;
89
111
  //# sourceMappingURL=index.cjs.map
@@ -1 +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"],"sources":["../../src/web/index.ts"],"sourcesContent":["import { throwWithCode } from '@towns-protocol/utils'\nimport { EncryptedData, Err } from '@towns-protocol/proto'\nimport { AES_GCM_DERIVED_ALGORITHM } from '@towns-protocol/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"],"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,sDAC5B,4CAAe,EAAE,cAAc,UAAU,8BAA8BC,2BAAI,cAAc;CAE7F,MAAM,EAAE,KAAK,IAAI,GAAG,MAAM,eAAe,UAAU;CACnD,MAAM,aAAa,mBAAmB,cAAc,WAAW;AAC/D,QAAO,cAAc,YAAY,KAAK,GAAG;AAC5C"}
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-protocol/utils'\nimport { EncryptedData, Err } from '@towns-protocol/proto'\nimport { AES_GCM_DERIVED_ALGORITHM } from '@towns-protocol/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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"}
@@ -14,8 +14,15 @@ declare function encryptAESGCM(data: Uint8Array, key?: Uint8Array, iv?: Uint8Arr
14
14
  }>;
15
15
  declare function decryptAESGCM(data: Uint8Array | string, key: Uint8Array, iv: Uint8Array): Promise<Uint8Array>;
16
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
+ }>;
17
24
  //# sourceMappingURL=index.d.ts.map
18
25
 
19
26
  //#endregion
20
- export { base64ToUint8Array, decryptAESGCM, decryptDerivedAESGCM, deriveKeyAndIV, encryptAESGCM, uint8ArrayToBase64 };
27
+ export { base64ToUint8Array, decryptAESGCM, decryptDerivedAESGCM, deriveKeyAndIV, encryptAESGCM, encryptChunkedAESGCM, uint8ArrayToBase64 };
21
28
  //# sourceMappingURL=index.d.cts.map
@@ -1 +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"}
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"}
@@ -14,8 +14,15 @@ declare function encryptAESGCM(data: Uint8Array, key?: Uint8Array, iv?: Uint8Arr
14
14
  }>;
15
15
  declare function decryptAESGCM(data: Uint8Array | string, key: Uint8Array, iv: Uint8Array): Promise<Uint8Array>;
16
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
+ }>;
17
24
  //# sourceMappingURL=index.d.ts.map
18
25
 
19
26
  //#endregion
20
- export { base64ToUint8Array, decryptAESGCM, decryptDerivedAESGCM, deriveKeyAndIV, encryptAESGCM, uint8ArrayToBase64 };
27
+ export { base64ToUint8Array, decryptAESGCM, decryptDerivedAESGCM, deriveKeyAndIV, encryptAESGCM, encryptChunkedAESGCM, uint8ArrayToBase64 };
21
28
  //# sourceMappingURL=index.d.ts.map
@@ -1 +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"}
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"}
package/dist/web/index.js CHANGED
@@ -77,7 +77,28 @@ async function decryptDerivedAESGCM(keyPhrase, encryptedData) {
77
77
  const ciphertext = base64ToUint8Array(encryptedData.ciphertext);
78
78
  return decryptAESGCM(ciphertext, key, iv);
79
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
+ }
80
101
 
81
102
  //#endregion
82
- export { base64ToUint8Array, decryptAESGCM, decryptDerivedAESGCM, deriveKeyAndIV, encryptAESGCM, uint8ArrayToBase64 };
103
+ export { base64ToUint8Array, decryptAESGCM, decryptDerivedAESGCM, deriveKeyAndIV, encryptAESGCM, encryptChunkedAESGCM, uint8ArrayToBase64 };
83
104
  //# sourceMappingURL=index.js.map
@@ -1 +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"],"sources":["../../src/web/index.ts"],"sourcesContent":["import { throwWithCode } from '@towns-protocol/utils'\nimport { EncryptedData, Err } from '@towns-protocol/proto'\nimport { AES_GCM_DERIVED_ALGORITHM } from '@towns-protocol/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"],"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,cAAc,0BAC5B,gBAAe,EAAE,cAAc,UAAU,8BAA8B,IAAI,cAAc;CAE7F,MAAM,EAAE,KAAK,IAAI,GAAG,MAAM,eAAe,UAAU;CACnD,MAAM,aAAa,mBAAmB,cAAc,WAAW;AAC/D,QAAO,cAAc,YAAY,KAAK,GAAG;AAC5C"}
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-protocol/utils'\nimport { EncryptedData, Err } from '@towns-protocol/proto'\nimport { AES_GCM_DERIVED_ALGORITHM } from '@towns-protocol/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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@towns-protocol/sdk-crypto",
3
- "version": "0.0.370",
3
+ "version": "0.0.372",
4
4
  "packageManager": "yarn@3.8.0",
5
5
  "type": "module",
6
6
  "main": "./dist/web/index.cjs",
@@ -16,9 +16,9 @@
16
16
  "watch": "tsdown --watch"
17
17
  },
18
18
  "dependencies": {
19
- "@towns-protocol/encryption": "^0.0.370",
20
- "@towns-protocol/proto": "^0.0.370",
21
- "@towns-protocol/utils": "^0.0.370"
19
+ "@towns-protocol/encryption": "^0.0.372",
20
+ "@towns-protocol/proto": "^0.0.372",
21
+ "@towns-protocol/utils": "^0.0.372"
22
22
  },
23
23
  "devDependencies": {
24
24
  "@bufbuild/protobuf": "^2.9.0",
@@ -50,5 +50,5 @@
50
50
  "access": "public"
51
51
  },
52
52
  "sideEffects": false,
53
- "gitHead": "a39b3d31251fbe2ec4f6c0902d9a5195974e8b9e"
53
+ "gitHead": "064d93eb0334ff96e7781dc951e99f41accca3c2"
54
54
  }