diff --git a/package.json b/package.json index 99fa6de0..a8194218 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ }, "dependencies": { "@akord/akord-auth": "0.29.1", - "@akord/crypto": "0.14.0", + "@akord/crypto": "0.15.0-beta.0", "@akord/ts-cacheable": "1.0.11", "axios": "^0.24.0", "fetch-undici": "^3.0.1", diff --git a/src/akord.ts b/src/akord.ts index dac06c6f..1168c089 100644 --- a/src/akord.ts +++ b/src/akord.ts @@ -26,7 +26,6 @@ export class Akord { public membership: MembershipService; public vault: VaultService; public stack: StackService; - public file: FileService; public note: NoteService; public manifest: ManifestService; public profile: ProfileService; @@ -48,7 +47,6 @@ export class Akord { this.memo = new MemoService(wallet, this.api); this.folder = new FolderService(wallet, this.api); this.stack = new StackService(wallet, this.api); - this.file = new FileService(wallet, this.api); this.note = new NoteService(wallet, this.api); this.manifest = new ManifestService(wallet, this.api); this.membership = new MembershipService(wallet, this.api); diff --git a/src/core/file.ts b/src/core/file.ts index 813a35b7..d4051556 100644 --- a/src/core/file.ts +++ b/src/core/file.ts @@ -1,9 +1,8 @@ import { Service } from "./service"; import { protocolTags, encryptionTags as encTags, fileTags, dataTags, smartweaveTags } from "../constants"; -import { base64ToArray, digestRaw, initDigest, signHash } from "@akord/crypto"; +import { AUTH_TAG_LENGTH_IN_BYTES, IV_LENGTH_IN_BYTES, base64ToArray, digestRaw, initDigest, signHash } from "@akord/crypto"; import { Logger } from "../logger"; import { ApiClient } from "../api/api-client"; -import { v4 as uuid } from "uuid"; import { FileLike, FileSource } from "../types/file"; import { Blob } from "buffer"; import { Tag, Tags } from "../types/contract"; @@ -14,13 +13,12 @@ import { FileVersion } from "../types"; import { Readable } from "stream"; import { BadRequest } from "../errors/bad-request"; import { StorageType } from "../types/node"; -import { InternalError } from "../errors/internal-error"; const DEFAULT_FILE_TYPE = "text/plain"; const BYTES_IN_MB = 1000000; const DEFAULT_CHUNK_SIZE_IN_BYTES = 10 * BYTES_IN_MB const MINIMAL_CHUNK_SIZE_IN_BYTES = 5 * BYTES_IN_MB -export const IV_LENGTH_IN_BYTES = 16; + class FileService extends Service { contentType = null as string; @@ -113,8 +111,8 @@ class FileService extends Service { options: FileUploadOptions ): Promise { const isPublic = options.public || this.isPublic - const iv : string[] = [] let encryptedKey : string + const { processedData, encryptionTags } = await this.processWriteRaw(await file.arrayBuffer()); const resourceHash = await digestRaw(new Uint8Array(processedData)); const fileSignatureTags = await this.getFileSignatureTags(resourceHash) @@ -122,14 +120,9 @@ class FileService extends Service { const resourceUri = resource.resourceUri; resourceUri.push(`hash:${resourceHash}`); if (!isPublic) { - const chunkIv = encryptionTags.find((tag) => tag.name === encTags.IV)?.value; - if (!chunkIv) { - throw new InternalError("Failed to encrypt data"); - } - iv.push(chunkIv); encryptedKey = encryptionTags.find((tag) => tag.name === encTags.ENCRYPTED_KEY).value; } - return { resourceUri: resourceUri, iv: iv, encryptedKey: encryptedKey } + return { resourceUri: resourceUri, encryptedKey: encryptedKey } } private async uploadChunked( @@ -146,11 +139,10 @@ class FileService extends Service { const isPublic = options.public || this.isPublic const chunkSize = options.chunkSize || DEFAULT_CHUNK_SIZE_IN_BYTES; - const chunkSizeWithAuthTag = isPublic ? chunkSize : chunkSize + IV_LENGTH_IN_BYTES; + const chunkSizeWithNonceAndIv = isPublic ? chunkSize : chunkSize + AUTH_TAG_LENGTH_IN_BYTES + IV_LENGTH_IN_BYTES; const numberOfChunks = Math.ceil(file.size / chunkSize); - const fileSize = isPublic ? file.size : file.size + numberOfChunks * IV_LENGTH_IN_BYTES; + const fileSize = isPublic ? file.size : file.size + numberOfChunks * (AUTH_TAG_LENGTH_IN_BYTES + IV_LENGTH_IN_BYTES); const digestObject = initDigest(); - const iv: Array = []; const etags: Array = []; @@ -162,11 +154,6 @@ class FileService extends Service { if (!isPublic) { encryptionTags = encryptedData.encryptionTags; - const chunkIv = encryptionTags.find((tag) => tag.name === encTags.IV)?.value; - if (!chunkIv) { - throw new InternalError("Failed to encrypt data"); - } - iv.push(chunkIv); if (!encryptedKey) { encryptedKey = encryptionTags.find((tag) => tag.name === encTags.ENCRYPTED_KEY).value; } @@ -198,8 +185,7 @@ class FileService extends Service { return { resourceUri: resource.resourceUri, numberOfChunks: numberOfChunks, - chunkSize: chunkSizeWithAuthTag, - iv: iv, + chunkSize: chunkSizeWithNonceAndIv, encryptedKey: encryptedKey }; } diff --git a/src/core/service.ts b/src/core/service.ts index 18a50461..41abff64 100644 --- a/src/core/service.ts +++ b/src/core/service.ts @@ -217,13 +217,12 @@ class Service { } else { let encryptedFile: EncryptedPayload; try { - encryptedFile = await this.dataEncrypter.encryptRaw(new Uint8Array(data), false, encryptedKey) as EncryptedPayload; + encryptedFile = await this.dataEncrypter.encryptRaw(new Uint8Array(data), { prefixCiphertextWithIv: true, encode: false, encryptedKey: encryptedKey }) as EncryptedPayload; } catch (error) { throw new IncorrectEncryptionKey(error); } processedData = encryptedFile.encryptedData.ciphertext as ArrayBuffer; const { address } = await this.getActiveKey(); - tags.push(new Tag(encryptionTags.IV, encryptedFile.encryptedData.iv)) tags.push(new Tag(encryptionTags.ENCRYPTED_KEY, encryptedFile.encryptedKey)) tags.push(new Tag(encryptionTags.PUBLIC_ADDRESS, address)) } diff --git a/src/core/stack.ts b/src/core/stack.ts index 94debd92..976bd829 100644 --- a/src/core/stack.ts +++ b/src/core/stack.ts @@ -1,10 +1,11 @@ import { NodeService } from "./node"; import { actionRefs, functions, objectType } from "../constants"; -import { FileDownloadOptions, FileGetOptions, FileService, FileUploadOptions, FileVersionData, IV_LENGTH_IN_BYTES, createFileLike } from "./file"; +import { FileDownloadOptions, FileGetOptions, FileService, FileUploadOptions, FileVersionData, createFileLike } from "./file"; import { FileSource } from "../types/file"; import { FileVersion, NodeCreateOptions, Stack, StackCreateOptions, StackCreateResult, StackUpdateResult, StorageType, nodeType } from "../types"; import { StreamConverter } from "../util/stream-converter"; import { ReadableStream } from 'web-streams-polyfill/ponyfill/es2018'; +import { AUTH_TAG_LENGTH_IN_BYTES, IV_LENGTH_IN_BYTES, PROXY_DOWNLOAD_URL } from "@akord/crypto"; class StackService extends NodeService { public fileService = new FileService(this.wallet, this.api); @@ -133,10 +134,10 @@ class StackService extends NodeService { if (service.isPublic) { stream = file.fileData as ReadableStream } else { - const encryptedKey = version.encryptedKey || file.metadata.encryptedKey; - const iv = version.iv || file.metadata.iv?.split(','); - const streamChunkSize = version.chunkSize || version.size + IV_LENGTH_IN_BYTES; - stream = await service.dataEncrypter.decryptStream(file.fileData as ReadableStream, encryptedKey, iv, streamChunkSize); + const encryptedKey = file.metadata.encryptedKey; + const iv = file.metadata.iv?.split(','); + const streamChunkSize = version.chunkSize || version.size + AUTH_TAG_LENGTH_IN_BYTES + (iv ? 0 : IV_LENGTH_IN_BYTES); + stream = await service.dataEncrypter.decryptStream(file.fileData as ReadableStream, encryptedKey, streamChunkSize, iv); } let data: ReadableStream | ArrayBuffer; @@ -154,8 +155,8 @@ class StackService extends NodeService { * @param {number} [index] stack version index * @returns Promise with version name & data buffer */ - public async download(stackId: string, index: number = 0, options: FileDownloadOptions = {}): Promise { - let downloadPromise: Promise + public async download(stackId: string, index: number = 0, options: FileDownloadOptions = {}): Promise { + let downloadPromise: Promise if (typeof window === 'undefined' || !navigator.serviceWorker?.controller) { const { name, type, data } = await this.getVersion(stackId, index, { ...options, responseType: 'stream' }); const path = `${options.path}/${name}`; @@ -167,6 +168,8 @@ class StackService extends NodeService { const stack = new Stack(stackProto, stackProto.__keys__); const version = stack.getVersion(index); const id = version.getUri(StorageType.S3); + const url = `${service.api.config.gatewayurl}/internal/${id}` + const proxyUrl = `${PROXY_DOWNLOAD_URL}/${id}` await service.setVaultContext(stack.vaultId); const workerMessage = { @@ -174,7 +177,7 @@ class StackService extends NodeService { chunkSize: version.chunkSize, size: version.size, id: id, - url: `${service.api.config.gatewayurl}/internal/${id}` + url: url } as Record; if (!service.isPublic) { @@ -188,7 +191,7 @@ class StackService extends NodeService { downloadPromise = new Promise((resolve, reject) => { if (options.skipSave) { - resolve(); + resolve(proxyUrl); } else { const interval = setInterval(() => { const channel = new MessageChannel(); @@ -226,7 +229,7 @@ class StackService extends NodeService { } if (!options.skipSave) { const anchor = document.createElement('a'); - anchor.href = `/api/proxy/download/${id}`; + anchor.href = proxyUrl; document.body.appendChild(anchor); anchor.click(); }