/* eslint-disable no-unused-vars */
import { KeyPair } from './KeyPair.js';
/* eslint-enable no-unused-vars */
import { deriveSharedKey, deriveSharedKeyDeprecated } from './SharedKey.js'; // eslint-disable-line import/no-deprecated
import { Message, MessageType } from './models.js';
/* eslint-disable no-unused-vars */
import { PublicKey } from '../CryptoTypes.js';
/* eslint-enable no-unused-vars */
import {
concatArrays, decodeAesCbc, decodeAesGcm, encodeAesCbc, encodeAesGcm
} from '../impl/CipherHelpers.js';
const filterExceptions = (statement, exceptions) => {
try {
const message = statement();
return [true, message];
} catch (exception) {
if (!exceptions.some(exceptionMessage => exception.message.includes(exceptionMessage)))
throw exception;
}
return [false, undefined];
};
/**
* Encrypts and encodes messages between two parties.
*/
export default class MessageEncoder {
/**
* Creates message encoder around key pair.
* @param {KeyPair} keyPair Key pair.
*/
constructor(keyPair) {
/**
* @private
*/
this._keyPair = keyPair;
}
/**
* Public key used for message encoding.
* @returns {PublicKey} Public key used for message encoding.
*/
get publicKey() {
return this._keyPair.publicKey;
}
/**
* Tries to decode encoded message.
* @param {PublicKey} recipientPublicKey Recipient public key.
* @param {Message} encodedMessage Encoded message.
* @returns {TryDecodeResult} Tuple containing decoded status and message.
*/
tryDecode(recipientPublicKey, encodedMessage) {
if (MessageType.ENCRYPTED !== encodedMessage.messageType)
throw new Error('invalid message format');
let [result, message] = filterExceptions(
() => decodeAesGcm(deriveSharedKey, this._keyPair, recipientPublicKey, encodedMessage.message),
['Unsupported state or unable to authenticate data']
);
if (result)
return { isDecoded: true, message };
[result, message] = filterExceptions(
// eslint-disable-next-line import/no-deprecated
() => decodeAesCbc(deriveSharedKeyDeprecated, this._keyPair, recipientPublicKey, encodedMessage.message),
[
'bad decrypt',
'wrong final block length',
'Invalid initialization vector'
]
);
if (result)
return { isDecoded: true, message };
return { isDecoded: false, message: encodedMessage };
}
/**
* Encodes message to recipient using recommended format.
* @param {PublicKey} recipientPublicKey Recipient public key.
* @param {Uint8Array} message Message to encode.
* @returns {Message} Encrypted and encoded message.
*/
encode(recipientPublicKey, message) {
const { tag, initializationVector, cipherText } = encodeAesGcm(deriveSharedKey, this._keyPair, recipientPublicKey, message);
const encodedMessage = new Message();
encodedMessage.messageType = MessageType.ENCRYPTED;
encodedMessage.message = concatArrays(tag, initializationVector, cipherText);
return encodedMessage;
}
/**
* Encodes message to recipient using recommended format.
* @deprecated This function is only provided for compatability with older NEM messages.
* Please use `encode` in any new code.
* @param {PublicKey} recipientPublicKey Recipient public key.
* @param {Uint8Array} message Message to encode.
* @returns {Message} Encrypted and encoded message.
*/
encodeDeprecated(recipientPublicKey, message) {
// eslint-disable-next-line import/no-deprecated
const encoded = encodeAesCbc(deriveSharedKeyDeprecated, this._keyPair, recipientPublicKey, message);
const encodedMessage = new Message();
encodedMessage.messageType = MessageType.ENCRYPTED;
encodedMessage.message = concatArrays(encoded.salt, encoded.initializationVector, encoded.cipherText);
return encodedMessage;
}
}
// region type declarations
/**
* Result of a try decode operation.
* @class
* @typedef {object} TryDecodeResult
* @property {boolean} isDecoded \c true if message has been decoded and decrypted; \c false otherwise.
* @property {Uint8Array|Message} message Decoded message when `isDecoded` is \c true; encoded message otherwise.
*/
// endregion