var _a, _EncryptedLogPayload_decryptOverhead, _EncryptedLogPayload_decryptIncomingBody;
import { __classPrivateFieldGet } from "tslib";
import { AztecAddress, Fr, NotOnCurveError, PRIVATE_LOG_SIZE_IN_FIELDS, Point, PrivateLog, derivePublicKeyFromSecretKey, } from '@aztec/circuits.js';
import { randomBytes } from '@aztec/foundation/crypto';
import { BufferReader, numToUInt16BE, serializeToBuffer } from '@aztec/foundation/serialize';
import { decrypt, encrypt } from './encryption_util.js';
// Below constants should match the values defined in aztec-nr/aztec/src/encrypted_logs/payload.nr.
const ENCRYPTED_PAYLOAD_SIZE_IN_BYTES = (PRIVATE_LOG_SIZE_IN_FIELDS - 1) * 31;
// The incoming header is 48 bytes
// 32 bytes for the address, and 16 bytes padding to follow PKCS#7
const HEADER_SIZE = 48;
// Padding added to the overhead to make the size of the incoming body ciphertext a multiple of 16.
const OVERHEAD_PADDING = 15;
const OVERHEAD_SIZE = 32 /* eph_pk */ + HEADER_SIZE /* incoming_header */ + OVERHEAD_PADDING; /* padding */
const PLAINTEXT_LENGTH_SIZE = 2;
const MAX_PRIVATE_LOG_PLAINTEXT_SIZE_IN_BYTES = ENCRYPTED_PAYLOAD_SIZE_IN_BYTES - OVERHEAD_SIZE - PLAINTEXT_LENGTH_SIZE - 1; /* aes padding */
function encryptedBytesToFields(encrypted) {
    const fields = [];
    const numFields = Math.ceil(encrypted.length / 31);
    for (let i = 0; i < numFields; i++) {
        fields.push(new Fr(encrypted.subarray(i * 31, (i + 1) * 31)));
    }
    return fields;
}
function fieldsToEncryptedBytes(fields) {
    return Buffer.concat(fields.map(f => f.toBuffer().subarray(1)));
}
class Overhead {
    constructor(ephPk, incomingHeader) {
        this.ephPk = ephPk;
        this.incomingHeader = incomingHeader;
    }
    static fromBuffer(reader) {
        const ephPk = Point.fromCompressedBuffer(reader.readBytes(Point.COMPRESSED_SIZE_IN_BYTES));
        const incomingHeader = reader.readBytes(HEADER_SIZE);
        // Advance the index to skip the padding.
        reader.readBytes(OVERHEAD_PADDING);
        return new Overhead(ephPk, incomingHeader);
    }
}
/**
 * Encrypted log payload with a tag used for retrieval by clients.
 */
export class EncryptedLogPayload {
    constructor(
    /**
     * Note discovery tag.
     */
    tag, 
    /**
     * Address of a contract that emitted the log.
     */
    contractAddress, 
    /**
     * Decrypted incoming body.
     */
    incomingBodyPlaintext) {
        this.tag = tag;
        this.contractAddress = contractAddress;
        this.incomingBodyPlaintext = incomingBodyPlaintext;
    }
    generatePayload(ephSk, recipient, rand = randomBytes) {
        const addressPoint = recipient.toAddressPoint();
        const ephPk = derivePublicKeyFromSecretKey(ephSk);
        const incomingHeaderCiphertext = encrypt(this.contractAddress.toBuffer(), ephSk, addressPoint);
        if (incomingHeaderCiphertext.length !== HEADER_SIZE) {
            throw new Error(`Invalid incoming header size: ${incomingHeaderCiphertext.length}`);
        }
        const overhead = serializeToBuffer(ephPk.toCompressedBuffer(), incomingHeaderCiphertext, Buffer.alloc(OVERHEAD_PADDING));
        if (overhead.length !== OVERHEAD_SIZE) {
            throw new Error(`Invalid ciphertext overhead size. Expected ${OVERHEAD_SIZE}. Got ${overhead.length}.`);
        }
        if (this.incomingBodyPlaintext.length > MAX_PRIVATE_LOG_PLAINTEXT_SIZE_IN_BYTES) {
            throw new Error(`Incoming body plaintext cannot be more than ${MAX_PRIVATE_LOG_PLAINTEXT_SIZE_IN_BYTES} bytes.`);
        }
        const numPaddedBytes = MAX_PRIVATE_LOG_PLAINTEXT_SIZE_IN_BYTES - this.incomingBodyPlaintext.length;
        const paddedIncomingBodyPlaintextWithLength = Buffer.concat([
            numToUInt16BE(this.incomingBodyPlaintext.length),
            this.incomingBodyPlaintext,
            rand(numPaddedBytes),
        ]);
        const incomingBodyCiphertext = encrypt(paddedIncomingBodyPlaintextWithLength, ephSk, addressPoint);
        const encryptedPayload = serializeToBuffer(overhead, incomingBodyCiphertext);
        const logFields = [this.tag, ...encryptedBytesToFields(encryptedPayload)];
        if (logFields.length !== PRIVATE_LOG_SIZE_IN_FIELDS) {
            throw new Error(`Expected private log payload to have ${PRIVATE_LOG_SIZE_IN_FIELDS} fields. Got ${logFields.length}.`);
        }
        return new PrivateLog(logFields);
    }
    /**
     * Decrypts a ciphertext as an incoming log.
     *
     * This is executable by the recipient of the note, and uses the addressSecret to decrypt the payload.
     *
     * @param payload - The payload for the log
     * @param addressSecret - The address secret, used to decrypt the logs
     * @returns The decrypted log payload
     */
    static decryptAsIncoming(payload, addressSecret) {
        try {
            const logFields = payload.fields;
            const tag = logFields[0];
            const reader = BufferReader.asReader(fieldsToEncryptedBytes(logFields.slice(1)));
            const overhead = Overhead.fromBuffer(reader);
            const { contractAddress } = __classPrivateFieldGet(this, _a, "m", _EncryptedLogPayload_decryptOverhead).call(this, overhead, { addressSecret });
            const ciphertext = reader.readToEnd();
            const incomingBodyPlaintext = __classPrivateFieldGet(this, _a, "m", _EncryptedLogPayload_decryptIncomingBody).call(this, ciphertext, addressSecret, overhead.ephPk);
            return new _a(tag, contractAddress, incomingBodyPlaintext);
        }
        catch (e) {
            // Following error messages are expected to occur when decryption fails
            if (!this.isAcceptableError(e)) {
                // If we encounter an unexpected error, we rethrow it
                throw e;
            }
            return;
        }
    }
    /**
     * Similar to `decryptAsIncoming`. Except that this is for the payload coming from public, which has tightly packed
     * bytes that don't have 0 byte at the beginning of every 32 bytes.
     * And the incoming body is of variable size.
     */
    static decryptAsIncomingFromPublic(payload, addressSecret) {
        try {
            const reader = BufferReader.asReader(payload);
            const tag = reader.readObject(Fr);
            const overhead = Overhead.fromBuffer(reader);
            const { contractAddress } = __classPrivateFieldGet(this, _a, "m", _EncryptedLogPayload_decryptOverhead).call(this, overhead, { addressSecret });
            // The incoming can be of variable size, so we read until the end
            const ciphertext = reader.readToEnd();
            const incomingBodyPlaintext = __classPrivateFieldGet(this, _a, "m", _EncryptedLogPayload_decryptIncomingBody).call(this, ciphertext, addressSecret, overhead.ephPk);
            return new _a(tag, contractAddress, incomingBodyPlaintext);
        }
        catch (e) {
            // Following error messages are expected to occur when decryption fails
            if (!this.isAcceptableError(e)) {
                // If we encounter an unexpected error, we rethrow it
                throw e;
            }
            return;
        }
    }
    static isAcceptableError(e) {
        return (e instanceof NotOnCurveError ||
            e.message.endsWith('is greater or equal to field modulus.') ||
            e.message.startsWith('Invalid AztecAddress length') ||
            e.message.startsWith('Selector must fit in') ||
            e.message.startsWith('Attempted to read beyond buffer length') ||
            e.message.startsWith('RangeError [ERR_BUFFER_OUT_OF_BOUNDS]:'));
    }
    toBuffer() {
        return serializeToBuffer(this.tag, this.contractAddress.toBuffer(), this.incomingBodyPlaintext);
    }
}
_a = EncryptedLogPayload, _EncryptedLogPayload_decryptOverhead = function _EncryptedLogPayload_decryptOverhead(overhead, { addressSecret }) {
    let contractAddress = AztecAddress.ZERO;
    if (addressSecret) {
        const incomingHeader = decrypt(overhead.incomingHeader, addressSecret, overhead.ephPk);
        contractAddress = AztecAddress.fromBuffer(incomingHeader);
    }
    return {
        contractAddress,
    };
}, _EncryptedLogPayload_decryptIncomingBody = function _EncryptedLogPayload_decryptIncomingBody(ciphertext, secret, publicKey) {
    const decrypted = decrypt(ciphertext, secret, publicKey);
    const length = decrypted.readUint16BE(0);
    return decrypted.subarray(2, 2 + length);
};
//# sourceMappingURL=data:application/json;base64, 