"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.sign = exports.makeSigner = exports.assertSigner = exports.makePrivateKeySigner = exports.recoverAddress = exports.defaultSign = void 0;
// For ESM compatibility
const elliptic_1 = __importDefault(require("elliptic"));
const { ec } = elliptic_1.default;
const error_1 = require("../utils/error");
const bytes_1 = require("../utils/bytes");
const hash_1 = require("../utils/hash");
const hex_1 = require("../utils/hex");
const types_1 = require("../types");
const type_1 = require("../utils/type");
const UNCOMPRESSED_RECOVERY_ID = 27;
function hashWithEthereumPrefix(data) {
    const ethereumSignedMessagePrefix = `\x19Ethereum Signed Message:\n${data.length}`;
    const prefixBytes = new TextEncoder().encode(ethereumSignedMessagePrefix);
    return (0, hash_1.keccak256Hash)(prefixBytes, data);
}
/**
 * The default signer function that can be used for integrating with
 * other applications (e.g. wallets).
 *
 * @param data      The data to be signed
 * @param privateKey  The private key used for signing the data
 */
function defaultSign(data, privateKey) {
    const curve = new ec('secp256k1');
    const keyPair = curve.keyFromPrivate(privateKey);
    const hashedDigest = hashWithEthereumPrefix(data);
    const sigRaw = curve.sign(hashedDigest, keyPair, { canonical: true, pers: undefined });
    if (sigRaw.recoveryParam === null) {
        throw new error_1.BeeError('signDigest recovery param was null');
    }
    const signature = new Uint8Array([
        ...sigRaw.r.toArray('be', 32),
        ...sigRaw.s.toArray('be', 32),
        sigRaw.recoveryParam + UNCOMPRESSED_RECOVERY_ID,
    ]);
    return signature;
}
exports.defaultSign = defaultSign;
function publicKeyToAddress(pubKey) {
    const pubBytes = pubKey.encode('array', false);
    return (0, hash_1.keccak256Hash)(pubBytes.slice(1)).slice(12);
}
/**
 * Recovers the ethereum address from a given signature.
 *
 * Can be used for verifying a piece of data when the public key is
 * known.
 *
 * @param signature The signature
 * @param digest    The digest of the data
 *
 * @returns the recovered address
 */
function recoverAddress(signature, digest) {
    const curve = new ec('secp256k1');
    const sig = {
        r: signature.slice(0, 32),
        s: signature.slice(32, 64),
    };
    const recoveryParam = signature[64] - UNCOMPRESSED_RECOVERY_ID;
    const hash = hashWithEthereumPrefix(digest);
    const recPubKey = curve.recoverPubKey(hash, sig, recoveryParam);
    return publicKeyToAddress(recPubKey);
}
exports.recoverAddress = recoverAddress;
/**
 * Creates a singer object that can be used when the private key is known.
 *
 * @param privateKey The private key
 */
function makePrivateKeySigner(privateKey) {
    const curve = new ec('secp256k1');
    const keyPair = curve.keyFromPrivate(privateKey);
    const address = publicKeyToAddress(keyPair.getPublic());
    return {
        sign: (digest) => defaultSign(digest, privateKey),
        address,
    };
}
exports.makePrivateKeySigner = makePrivateKeySigner;
function assertSigner(signer) {
    if (!(0, type_1.isStrictlyObject)(signer)) {
        throw new TypeError('Signer must be an object!');
    }
    const typedSigner = signer;
    if (!(0, bytes_1.isBytes)(typedSigner.address, 20)) {
        throw new TypeError("Signer's address must be Uint8Array with 20 bytes!");
    }
    if (typeof typedSigner.sign !== 'function') {
        throw new TypeError('Signer sign property needs to be function!');
    }
}
exports.assertSigner = assertSigner;
function makeSigner(signer) {
    if (typeof signer === 'string') {
        const hexKey = (0, hex_1.makeHexString)(signer, 64);
        const keyBytes = (0, hex_1.hexToBytes)(hexKey); // HexString is verified for 64 length => 32 is guaranteed
        return makePrivateKeySigner(keyBytes);
    }
    else if (signer instanceof Uint8Array) {
        (0, bytes_1.assertBytes)(signer, 32);
        return makePrivateKeySigner(signer);
    }
    assertSigner(signer);
    return signer;
}
exports.makeSigner = makeSigner;
function sign(signer, data) {
    return __awaiter(this, void 0, void 0, function* () {
        const result = yield signer.sign((0, bytes_1.wrapBytesWithHelpers)(data));
        if (typeof result === 'string') {
            const hexString = (0, hex_1.makeHexString)(result, types_1.SIGNATURE_HEX_LENGTH);
            return (0, hex_1.hexToBytes)(hexString);
        }
        if (result instanceof Uint8Array) {
            (0, bytes_1.assertBytes)(result, types_1.SIGNATURE_BYTES_LENGTH);
            return result;
        }
        throw new TypeError('Invalid output of sign function!');
    });
}
exports.sign = sign;
