Source: crypto/CryptoApi.ts

/**
 * @author Erik Haßlmeyer <erik.hasslmeyer@refinio.net>
 * @copyright REFINIO GmbH 2022
 * @license CC-BY-NC-SA-2.5; portions MIT License
 * @version 0.0.1
 */

import {createError} from '../errors';
import type {KeyPair, Nonce, PublicKey} from './encryption';
import {
    decrypt,
    decryptWithEmbeddedNonce,
    deriveSymmetricKeyFromKeypair,
    encrypt,
    encryptAndEmbedNonce
} from './encryption';
import type {PublicSignKey, SignKeyPair} from './sign';
import {sign} from './sign';
import {SymmetricCryptoApi, SymmetricCryptoApiWithKeys} from './SymmetricCryptoApi';

/**
 * This api is a wrapper for crypto functions that operate on secret keys.
 *
 * Through this wrapper you can expose crypto functionality without exposing the private key. This
 * is exactly what the keychain implementation does. It will never give you direct access to
 * private keys, only the functions needed to work with those keys.
 */
export class CryptoApi {
    readonly #encryptionKeyPair: KeyPair;
    readonly #signKeyPair?: SignKeyPair;

    get publicEncryptionKey(): PublicKey {
        return this.#encryptionKeyPair.publicKey;
    }

    get publicSignKey(): PublicSignKey {
        if (this.#signKeyPair === undefined) {
            throw createError('CYAPI-PUBSK');
        }

        return this.#signKeyPair.publicKey;
    }

    /**
     * Construct a new crypt api wrapper.
     *
     * @param {KeyPair} encryptionKeyPair - keypair for encryption
     * @param {SignKeyPair} signKeyPair - keypair for signing
     */
    constructor(encryptionKeyPair: KeyPair, signKeyPair?: SignKeyPair) {
        this.#encryptionKeyPair = encryptionKeyPair;
        this.#signKeyPair = signKeyPair;
    }

    /**
     * Same as encryption.ts:encrypt
     *
     * @param {Uint8Array} data
     * @param {PublicKey} otherPublicKey
     * @param {Nonce} nonce
     * @returns {Uint8Array}
     */
    encrypt(data: Uint8Array, otherPublicKey: PublicKey, nonce: Nonce): Uint8Array {
        return encrypt(data, this.#encryptionKeyPair.secretKey, otherPublicKey, nonce);
    }

    /**
     * Same as encryption.ts:encryptAndEmbedNonce
     *
     * @param {Uint8Array} data
     * @param {PublicKey} otherPublicKey
     * @param {Nonce} nonce
     * @returns {Uint8Array}
     */
    encryptAndEmbedNonce(data: Uint8Array, otherPublicKey: PublicKey, nonce?: Nonce): Uint8Array {
        return encryptAndEmbedNonce(data, this.#encryptionKeyPair.secretKey, otherPublicKey, nonce);
    }

    /**
     * Same as encryption.ts:decrypt
     *
     * @param {Uint8Array} cypher
     * @param {PublicKey} otherPublicKey
     * @param {Nonce} nonce
     * @returns {Uint8Array}
     */
    decrypt(cypher: Uint8Array, otherPublicKey: PublicKey, nonce: Nonce): Uint8Array {
        return decrypt(cypher, this.#encryptionKeyPair.secretKey, otherPublicKey, nonce);
    }

    /**
     * Same as encryption.ts:decryptWithEmbeddedNonce
     *
     * @param {Uint8Array} cypherAndNonce
     * @param {PublicKey} otherPublicKey
     * @returns {Uint8Array}
     */
    decryptWithEmbeddedNonce(cypherAndNonce: Uint8Array, otherPublicKey: PublicKey): Uint8Array {
        return decryptWithEmbeddedNonce(
            cypherAndNonce,
            this.#encryptionKeyPair.secretKey,
            otherPublicKey
        );
    }

    /**
     * Construct an encryption/decryption api based on the public key of someone else.
     *
     * All encryption & decryption calls from this crypto API, require the public key of
     * somebody else. By using the api returned by this function, you do not have to specify the
     * key of the other side each time. This is also slightly faster if multiple functions are
     * called, because this call derives the symmetric key only once.
     *
     * @param {PublicKey} otherPublicKey
     * @returns {EncryptionApi}
     */
    createEncryptionApiWithPerson(otherPublicKey: PublicKey): SymmetricCryptoApi {
        const symmetricKey = deriveSymmetricKeyFromKeypair(
            this.#encryptionKeyPair.secretKey,
            otherPublicKey
        );
        return new SymmetricCryptoApi(symmetricKey);
    }

    /**
     * Same as createCryptoApiWith, but we also store the public keys of the participants.
     *
     * @param {PublicKey} otherPublicKey
     * @returns {SymmetricCryptoApi}
     */
    createEncryptionApiWithKeysAndPerson(otherPublicKey: PublicKey): SymmetricCryptoApiWithKeys {
        const symmetricKey = deriveSymmetricKeyFromKeypair(
            this.#encryptionKeyPair.secretKey,
            otherPublicKey
        );
        return new SymmetricCryptoApiWithKeys(
            symmetricKey,
            this.publicEncryptionKey,
            otherPublicKey
        );
    }

    /**
     * Sign the passed data.
     *
     * This only returns the signature, not a complete signed message.
     *
     * @param {Uint8Array} data
     * @returns {Uint8Array}
     */
    sign(data: Uint8Array): Uint8Array {
        if (this.#signKeyPair === undefined) {
            throw createError('CYAPI-SIGN');
        }

        return sign(data, this.#signKeyPair.secretKey);
    }
}