diff --git a/src/react-app-env.d.ts b/src/react-app-env.d.ts
index 12c6ef2..56c759b 100644
--- a/src/react-app-env.d.ts
+++ b/src/react-app-env.d.ts
@@ -1,16 +1,147 @@
///
-// TODO: Type the rest of the API and contribute it to the wormhole-crypto project
declare module 'wormhole-crypto' {
- export class Keychain {
- constructor(key: Uint8Array, salt: Uint8Array)
-
- encryptStream(ReadableStream): Promise
-
- decryptStream(ReadableStream): Promise
+ /**
+ * The encrypted byte range that is needed to decrypt the client's specified range.
+ */
+ export type ByteRange = {
+ offset: number
+ length: number
}
- export const plaintextSize = number => number
+ /**
+ * The metadata buffer to encrypt.
+ */
+ export type Meta = Uint8Array
- export const encryptedSize = number => number
+ /**
+ * A WHATWG readable stream used as a data source for the plaintext stream.
+ */
+ export type EncryptedStream = ReadableStream
+
+ /**
+ * @param streams An array of `ReadableStream` objects, one for each of the requested ranges.
+ * @returns Contains the plaintext data for the client's desired byte range.
+ */
+ export type DecryptFn = (streams: ReadableStream[]) => ReadableStream
+
+ export type DecryptedStreamRange = {
+ ranges: ByteRange[]
+ decrypt: DecryptFn
+ }
+
+ export class Keychain {
+ /**
+ * Create a new keychain object. The keychain can be used to create encryption streams, decryption streams, and to encrypt or decrypt a "metadata" buffer.
+ *
+ * @param key The main key. This should be 16 bytes in length. If a string is given, then it should be a base64-encoded string. If the argument is null, then a key will be automatically generated.
+ * @param salt The salt. This should be 16 bytes in length. If a string is given, then it should be a base64-encoded string. If this argument is null, then a salt will be automatically generated.
+ */
+ constructor(
+ key: Uint8Array | string | null = null,
+ salt: Uint8Array | string | null = null
+ )
+
+ /**
+ * The main key.
+ */
+ key: Uint8Array
+
+ /**
+ * The main key as a base64-encoded string.
+ */
+ keyB64: string
+
+ /**
+ * The salt.
+ *
+ * Implementation note: The salt is used to derive the (internal) metadata key and authentication token.
+ */
+ salt: Uint8Array
+
+ /**
+ * The salt as a base64-encoded string.
+ */
+ saltB64: string
+
+ /**
+ * Returns a `Promise` which resolves to the authentication token. By default, the authentication token is automatically derived from the main key using HKDF SHA-256.
+ *
+ * In Wormhole, the authentication token is used to communicate with the server and prove that the client has permission to fetch data for a room. Without a valid authentication token, the server will not return the encrypted room metadata or allow downloading the encrypted file data.
+ *
+ * Since the authentication token is derived from the main key, the client presents it to the Wormhole server as a "reader token" to prove that it is in possession of the main key without revealing the main key to the server.
+ *
+ * For destructive operations, like modifying the room, the client instead presents a "writer token", which is not derived from the main key but is provided by the server to the room creator who overrides the keychain authentication token by calling `keychain.setAuthToken(authToken)` with the "writer token".
+ */
+ authToken(): Promise
+
+ /**
+ * Returns a `Promise` that resolves to the authentication token as a base64-encoded string.
+ */
+ authTokenB64(): Promise
+
+ /**
+ * Returns a `Promise` that resolves to the HTTP header value to be provided to the Wormhole server. It contains the authentication token.
+ */
+ authHeader(): Promise
+
+ /**
+ * Update the keychain authentication token to `authToken`.
+ *
+ * @param authToken The authentication token. This should be 16 bytes in length. If a `string` is given, then it should be a base64-encoded string. If this argument is `null`, then an authentication token will be automatically generated.
+ */
+ setAuthToken(authToken: Uint8Array | string | null = null): void
+
+ /**
+ * @param stream A WHATWG readable stream used as a data source for the encrypted stream.
+ *
+ * @returns A `Promise` that resolves to a `ReadableStream` encryption stream that consumes the data in `stream` and returns an encrypted version. Data is encrypted with [Encrypted Content-Encoding for HTTP (RFC 8188)](https://tools.ietf.org/html/rfc8188).
+ */
+ encryptStream(stream: ReadableStream): Promise
+
+ /**
+ * Returns a `Promise` that resolves to a `ReadableStream` decryption stream that consumes the data in `encryptedStream` and returns a plaintext version.
+ *
+ * @param encryptedStream A WHATWG readable stream that was returned from encryptStream.
+ * @returns A `Promise` that resolves to a `ReadableStream` decryption stream that
+consumes the data in `encryptedStream` and returns a plaintext version.
+ */
+ decryptStream(encryptedStream: EncryptedStream): Promise
+
+ /**
+ * Returns a `Promise` that resolves to a object containing `ranges`, which is an array of objects containing `offset` and `length` integers specifying the encrypted byte ranges that are needed to decrypt the client's specified range, and a `decrypt` function.
+ *
+ * Once the client has gathered a stream for each byte range in `ranges`, the client should call `decrypt(streams)`, where `streams` is an array of `ReadableStream` objects, one for each of the requested ranges. `decrypt` will then return a `ReadableStream` containing the plaintext data for the client's desired byte range.
+ */
+ decryptStreamRange(
+ offset: number,
+ length: number,
+ totalEncryptedLength: number
+ ): Promise
+
+ /**
+ * Implementation note: The metadata key is automatically derived from the main key using HKDF SHA-256. The value is not user-controlled.
+ *
+ * Implementation note: The initialization vector (IV) is automatically generated and included in the encrypted output. No need to generate it or to manage it separately from the encrypted output.
+ *
+ * @returns A `Promise` that resolves to an encrypted version of `meta`. The metadata is encrypted with AES-GCM.
+ */
+ encryptMeta(meta: Meta): Promise
+
+ /**
+ * @param encryptedMeta The encrypted metadata buffer to decrypt.
+ * @returns A `Promise` that resolves to a decrypted version of `encryptedMeta`.
+ */
+ decryptMeta(encryptedMeta: Uint8Array): Promise
+ }
+
+ /**
+ * Given an encrypted size, return the corresponding plaintext size.
+ */
+ export function plaintextSize(encryptedSize: number): number
+
+ /**
+ * Given a plaintext size, return the corresponding encrypted size.
+ */
+ export function encryptedSize(plaintextSize: number): number
}
diff --git a/src/services/FileTransfer/FileTransfer.ts b/src/services/FileTransfer/FileTransfer.ts
index 687233b..8bc56e5 100644
--- a/src/services/FileTransfer/FileTransfer.ts
+++ b/src/services/FileTransfer/FileTransfer.ts
@@ -124,8 +124,13 @@ export class FileTransfer {
const encryptedFiles = await Promise.all(
filesToSeed.map(async file => {
+ // Force a type conversion here to prevent stream from being typed as a
+ // NodeJS.ReadableStream, which is the default overloaded return type
+ // for file.stream().
+ const stream = file.stream() as any as ReadableStream
+
const encryptedStream = await getKeychain(password).encryptStream(
- file.stream()
+ stream
)
// WebTorrent internally opens the ReadableStream for file data twice.