"use strict";
exports.__esModule = true;
/** @module signing */
var converter_1 = require("@iota/converter");
var kerl_1 = require("@iota/kerl");
var pad_1 = require("@iota/pad");
var Promise = require("bluebird");
var errors = require("../../errors");
require("../../typed-array");
var add_1 = require("./add");
exports.MIN_TRYTE_VALUE = -13;
exports.MAX_TRYTE_VALUE = 13;
exports.NUMBER_OF_SECURITY_LEVELS = 3;
exports.HASH_LENGTH = 243;
exports.FRAGMENT_LENGTH = (exports.HASH_LENGTH / exports.NUMBER_OF_SECURITY_LEVELS / converter_1.TRYTE_WIDTH) * exports.HASH_LENGTH;
exports.NUMBER_OF_FRAGMENT_CHUNKS = exports.FRAGMENT_LENGTH / exports.HASH_LENGTH;
exports.NORMALIZED_FRAGMENT_LENGTH = exports.HASH_LENGTH / converter_1.TRYTE_WIDTH / exports.NUMBER_OF_SECURITY_LEVELS;
/**
 * This method derives a subseed from a seed and a private key index.
 *
 * You can use the subseed to [derive private keys and their addresses](https://docs.iota.org/docs/client-libraries/0.1/how-to-guides/js/derive-addresses-from-private-keys).
 *
 * **Note:** If the given seed is less then 243 trits, 0 trits are appended to it until it is 243 trits long.
 *
 * ## Related methods
 *
 * To convert a seed from trytes to trits, use the [`trytesToTrits()`]{@link #module_converter.trytesToTrits} method.
 *
 * To derive a private key from the subseed, use the [`key()`]{@link #module_signing.key} method.
 *
 * @method subseed
 *
 * @summary Generates a subseed.
 *
 * @memberof module:signing
 *
 * @param {Int8Array} seed - A 243-trit seed to use to derive the subseed
 * @param {number} index - The private key index to use to derive the subseed
 *
 * @example
 * ```js
 * const seed = 'MYSUPERSECRETSEED...';
 * const subseed = Sign.subseed(Converter.trytesToTrits(seed), 0);
 * ```
 *
 * @return {Int8Array} subseed - A subseed in trits
 *
 * @throws {errors.ILLEGAL_SUBSEED_INDEX}: Make sure that the `index` argument is a number greater than 0.
 */
function subseed(seed, index) {
    if (!Number.isInteger(index) || index < 0) {
        throw new Error(errors.ILLEGAL_SUBSEED_INDEX);
    }
    var pad = pad_1.padTrits(Math.ceil(seed.length / exports.HASH_LENGTH) * exports.HASH_LENGTH);
    var subseedPreimage = add_1.add(pad(seed), converter_1.fromValue(index));
    var subseedTrits = new Int8Array(exports.HASH_LENGTH);
    var sponge = new kerl_1["default"]();
    sponge.absorb(subseedPreimage, 0, subseedPreimage.length);
    sponge.squeeze(subseedTrits, 0, exports.HASH_LENGTH);
    return subseedTrits;
}
exports.subseed = subseed;
/**
 * This method derives a private key from a subseed.
 *
 * You can use the private key to [derive an address](https://docs.iota.org/docs/client-libraries/0.1/how-to-guides/js/derive-addresses-from-private-keys) and to sign bundles that withdraw from that address.
 *
 * ## Related methods
 *
 * To generate a subseed, use the [`subseed()`]{@link #module_signing.subseed} method.
 *
 * @method key
 *
 * @summary Generates a private key.
 *
 * @memberof module:signing
 *
 * @param {Int8Array} subseedTrits - A subseed in trits
 * @param {number} numberOfFragments - The security level that you want the private key to have
 *
 * @example
 * ```js
 * const seed = 'MYSUPERSECRETSEED...';
 * const subseed = Signing.subseed(Converter.trytesToTrits(seed), 0);
 *
 * const privateKey = Signing.key(subseed, 2);
 * ```
 *
 * @return {Int8Array} privateKey - A private key in trits.
 *
 * @throws {errors.ILLEGAL_SUBSEED_LENGTH}: Make sure that the `subseedTrits` argument contains 243 trits.
 * @throws {errors.ILLEGAL_NUMBER_OF_FRAGMENTS}: Make sure that the `numberOfFragments` argument is a valid security level (between 1 and 3).
 */
function key(subseedTrits, numberOfFragments) {
    if (subseedTrits.length !== kerl_1["default"].HASH_LENGTH) {
        throw new Error(errors.ILLEGAL_SUBSEED_LENGTH);
    }
    if ([1, 2, 3].indexOf(numberOfFragments) === -1) {
        throw new Error(errors.ILLEGAL_NUMBER_OF_FRAGMENTS);
    }
    var keyTrits = new Int8Array(exports.FRAGMENT_LENGTH * numberOfFragments);
    var sponge = new kerl_1["default"]();
    sponge.absorb(subseedTrits, 0, subseedTrits.length);
    sponge.squeeze(keyTrits, 0, keyTrits.length);
    return keyTrits;
}
exports.key = key;
/**
 * This method derives key digests from a private key.
 *
 * You can use the key digests to [generate an address](https://docs.iota.org/docs/client-libraries/0.1/how-to-guides/js/derive-addresses-from-private-keys).
 *
 * ## Related methods
 *
 * To generate a private key, use the [`key()`]{@link #module_signing.key} method.
 *
 * @method digests
 *
 * @summary Generates key digests for a given private key.
 *
 * @memberof module:signing
 *
 * @param {Int8Array} key - Private key in trits
 *
 * @example
 * ```js
 * const seed = 'MYSUPERSECRETSEED...';
 * const subseed = Signing.subseed(Converter.trytesToTrits(seed), 0);
 *
 * const privateKey = Signing.key(subseed, 2);
 *
 * const digests = Signing.digests(privateKey);
 * ```
 *
 * @return {Int8Array} digests - Key digests in trits
 *
 * @throws {errors.ILLEGAL_KEY_LENGTH}: Make sure that the `key` argument contains 2,187, 4,374, or 6,561 trits.
 */
// tslint:disable-next-line no-shadowed-variable
function digests(key) {
    if (key.length === 0 || key.length % exports.FRAGMENT_LENGTH !== 0) {
        throw new Error(errors.ILLEGAL_KEY_LENGTH);
    }
    var numberOfFragments = key.length / exports.FRAGMENT_LENGTH;
    var digestsTrits = new Int8Array(numberOfFragments * exports.HASH_LENGTH);
    var sponge = new kerl_1["default"]();
    for (var i = 0; i < numberOfFragments; i++) {
        var buffer = key.slice(i * exports.FRAGMENT_LENGTH, (i + 1) * exports.FRAGMENT_LENGTH);
        for (var j = 0; j < exports.NUMBER_OF_FRAGMENT_CHUNKS; j++) {
            for (var k = 0; k < exports.MAX_TRYTE_VALUE - exports.MIN_TRYTE_VALUE; k++) {
                sponge.reset();
                sponge.absorb(buffer, j * exports.HASH_LENGTH, exports.HASH_LENGTH);
                sponge.squeeze(buffer, j * exports.HASH_LENGTH, exports.HASH_LENGTH);
            }
        }
        sponge.reset();
        sponge.absorb(buffer, 0, buffer.length);
        sponge.squeeze(digestsTrits, i * exports.HASH_LENGTH, exports.HASH_LENGTH);
    }
    return digestsTrits;
}
exports.digests = digests;
/**
 * This method derives a 243-trit address from the given key digests.
 *
 * ## Related methods
 *
 * To generate a private key, use the [`key()`]{@link #module_signing.key} method.
 *
 * @method address
 *
 * @summary Derives an address from the given key digests.
 *
 * @memberof module:signing
 *
 * @param {Int8Array} digests - Key digests in trits
 *
 * @example
 * ```js
 * const seed = 'MYSUPERSECRETSEED...';
 * const subseed = Signing.subseed(Converter.trytesToTrits(seed), 0);
 *
 * const privateKey = Signing.key(subseed, 2);
 *
 * const digests = Signing.digests(privateKey);
 *
 * const address = Signing.address(digests);
 * ```
 *
 * @return {Int8Array} address - Address in trits
 *
 * @throws {errors.ILLEGAL_DIGESTS_LENGTH}: Make sure that the `digests` argument contains a multiple of 243 trits.
 */
// tslint:disable-next-line no-shadowed-variable
function address(digests) {
    if (digests.length === 0 || digests.length % exports.HASH_LENGTH !== 0) {
        throw new Error(errors.ILLEGAL_DIGESTS_LENGTH);
    }
    var addressTrits = new Int8Array(exports.HASH_LENGTH);
    var sponge = new kerl_1["default"]();
    sponge.absorb(digests.slice(), 0, digests.length);
    sponge.squeeze(addressTrits, 0, exports.HASH_LENGTH);
    return addressTrits;
}
exports.address = address;
/**
 * @method digest
 *
 * @ignore
 *
 * @param {array} normalizedBundleFragment - Normalized bundle fragments in trits
 * @param {Int8Array} signatureFragment - Signature fragment trits
 *
 * @return {Int8Array} Digest trits
 */
function digest(normalizedBundleFragment, signatureFragment, // tslint:disable-line
normalizedBundleFragmentOffset, signatureFragmentOffset) {
    if (normalizedBundleFragmentOffset === void 0) { normalizedBundleFragmentOffset = 0; }
    if (signatureFragmentOffset === void 0) { signatureFragmentOffset = 0; }
    if (normalizedBundleFragment.length - normalizedBundleFragmentOffset < exports.NORMALIZED_FRAGMENT_LENGTH) {
        throw new Error(errors.ILLEGAL_NORMALIZED_FRAGMENT_LENGTH);
    }
    if (signatureFragment.length - signatureFragmentOffset < exports.FRAGMENT_LENGTH) {
        throw new Error(errors.ILLEGAL_SIGNATURE_FRAGMENT_LENGTH);
    }
    var buffer = signatureFragment.slice(signatureFragmentOffset, signatureFragmentOffset + exports.FRAGMENT_LENGTH);
    var digestTrits = new Int8Array(exports.HASH_LENGTH);
    var sponge = new kerl_1["default"]();
    for (var j = 0; j < exports.NUMBER_OF_FRAGMENT_CHUNKS; j++) {
        for (var k = normalizedBundleFragment[normalizedBundleFragmentOffset + j] - exports.MIN_TRYTE_VALUE; k-- > 0;) {
            sponge.reset();
            sponge.absorb(buffer, j * exports.HASH_LENGTH, exports.HASH_LENGTH);
            sponge.squeeze(buffer, j * exports.HASH_LENGTH, exports.HASH_LENGTH);
        }
    }
    sponge.reset();
    sponge.absorb(buffer, 0, buffer.length);
    sponge.squeeze(digestTrits, 0, digestTrits.length);
    return digestTrits;
}
exports.digest = digest;
/**
 * @method signatureFragment
 * @ignore
 * @param {array} normalizeBundleFragment - normalized bundle fragment
 * @param {keyFragment} keyFragment - key fragment trits
 *
 * @return {Int8Array} Signature Fragment trits
 */
function signatureFragment(normalizedBundleFragment, keyFragment, normalizedBundleFragmentOffset, keyFragmentOffset) {
    if (normalizedBundleFragmentOffset === void 0) { normalizedBundleFragmentOffset = 0; }
    if (keyFragmentOffset === void 0) { keyFragmentOffset = 0; }
    if (normalizedBundleFragment.length - normalizedBundleFragmentOffset < exports.NORMALIZED_FRAGMENT_LENGTH) {
        throw new Error(errors.ILLEGAL_NORMALIZED_FRAGMENT_LENGTH);
    }
    if (keyFragment.length - keyFragmentOffset < exports.FRAGMENT_LENGTH) {
        throw new Error(errors.ILLEGAL_KEY_FRAGMENT_LENGTH);
    }
    var signatureFragmentTrits = keyFragment.slice(keyFragmentOffset, keyFragmentOffset + exports.FRAGMENT_LENGTH);
    var sponge = new kerl_1["default"]();
    for (var j = 0; j < exports.NUMBER_OF_FRAGMENT_CHUNKS; j++) {
        for (var k = 0; k < exports.MAX_TRYTE_VALUE - normalizedBundleFragment[normalizedBundleFragmentOffset + j]; k++) {
            sponge.reset();
            sponge.absorb(signatureFragmentTrits, j * exports.HASH_LENGTH, exports.HASH_LENGTH);
            sponge.squeeze(signatureFragmentTrits, j * exports.HASH_LENGTH, exports.HASH_LENGTH);
        }
    }
    return signatureFragmentTrits;
}
exports.signatureFragment = signatureFragment;
function signatureFragments(seed, index, numberOfFragments, bundle, nativeGenerateSignatureFunction) {
    if (nativeGenerateSignatureFunction && typeof nativeGenerateSignatureFunction === 'function') {
        return nativeGenerateSignatureFunction(Array.prototype.slice.call(seed), index, numberOfFragments, Array.prototype.slice.call(bundle)).then(function (nativeSignature) { return new Int8Array(nativeSignature); });
    }
    var normalizedBundleHash = exports.normalizedBundle(bundle);
    var keyTrits = key(subseed(seed, index), numberOfFragments);
    var signature = new Int8Array(numberOfFragments * exports.FRAGMENT_LENGTH);
    for (var i = 0; i < numberOfFragments; i++) {
        signature.set(signatureFragment(normalizedBundleHash.slice(i * exports.NORMALIZED_FRAGMENT_LENGTH, (i + 1) * exports.NORMALIZED_FRAGMENT_LENGTH), keyTrits.slice(i * exports.FRAGMENT_LENGTH, (i + 1) * exports.FRAGMENT_LENGTH)), i * exports.FRAGMENT_LENGTH);
    }
    return Promise.resolve(signature);
}
exports.signatureFragments = signatureFragments;
/**
 * This method validates a signature by doing the following:
 *
 * - Normalizing the bundle hash
 * - Deriving the key digests of the address, using the normalized bundle hash and the signature
 * -.Deriving an address from the key digests
 * - Comparing the derived address to the `expectedAddress` argument to find out if they match
 *
 * If the addresses match, the signature is valid.
 *
 * For more information about signatures see the [documentation portal](https://docs.iota.org/docs/getting-started/0.1/clients/signatures).
 *
 * ## Related methods
 *
 * To convert trytes such as bundle hashes and addresses to trits, use the [`trytesToTrits()`]{@link #module_converter.trytesToTrits} method.
 *
 * @method validateSignatures
 *
 * @summary Validates the given signature, using the given bundle and address.
 *
 * @memberof module:signing
 *
 * @param {Int8Array} expectedAddress - Input address in trits
 * @param {Array<Int8Array>} signatureFragments - Signature fragments in trits
 * @param {Int8Array} bundle - Bundle hash in trits
 *
 * @example
 * ```js
 * let valid = Signing.validateSignatures(expectedAddress, signatureFragments, bundle);
 * ```
 *
 * @return {boolean} valid - Whether the signatures are valid.
 *
 * @throws {errors.ILLEGAL_BUNDLE_HASH_LENGTH}: Make sure that the `bundle` argument contains a 243-trit bundle hash.
 */
function validateSignatures(expectedAddress, signatureFragments, // tslint:disable-line
bundle) {
    if (bundle.length !== exports.HASH_LENGTH) {
        throw new Error(errors.ILLEGAL_BUNDLE_HASH_LENGTH);
    }
    var normalizedBundleFragments = [];
    var normalizedBundleHash = exports.normalizedBundle(bundle);
    // Split hash into 3 fragments
    for (var i = 0; i < exports.NUMBER_OF_SECURITY_LEVELS; i++) {
        normalizedBundleFragments[i] = normalizedBundleHash.slice(i * exports.NUMBER_OF_FRAGMENT_CHUNKS, (i + 1) * exports.NUMBER_OF_FRAGMENT_CHUNKS);
    }
    // Get digests
    var digestsTrits = new Int8Array(signatureFragments.length * exports.HASH_LENGTH);
    for (var i = 0; i < signatureFragments.length; i++) {
        var digestBuffer = digest(normalizedBundleFragments[i % exports.NUMBER_OF_SECURITY_LEVELS], signatureFragments[i]);
        for (var j = 0; j < exports.HASH_LENGTH; j++) {
            digestsTrits[i * exports.HASH_LENGTH + j] = digestBuffer[j];
        }
    }
    var actualAddress = address(digestsTrits);
    return expectedAddress.every(function (trit, i) { return trit === actualAddress[i]; });
}
exports.validateSignatures = validateSignatures;
/**
 * This method normalizes the given bundle hash to make sure that only around half of the private key is revealed when the bundle hash is signed.
 *
 * For more information about signatures see the [documentation portal](https://docs.iota.org/docs/getting-started/0.1/clients/signatures).
 *
 * To find out more about why the bundle hash is normalized, see [this answer on StackExchange](https://iota.stackexchange.com/questions/1588/why-is-the-bundle-hash-normalized).
 *
 * ## Related methods
 *
 * To convert a bundle hash from trytes to trits, use the [`trytesToTrits()`]{@link #module_converter.trytesToTrits} method.
 *
 * @method normalizedBundle
 *
 * @summary Normalizes the bundle hash.
 *
 * @memberof module:signing
 *
 * @param {Int8Array} bundle - Bundle hash in trits
 *
 * @example
 * ```js
 * let normalizedBundleHash = Signing.normalizedBundle(bundle);
 * ```
 *
 * @return {Int8Array} Normalized bundle hash in trits
 *
 * @throws {errors.ILLEGAL_BUNDLE_HASH_LENGTH}: Make sure that the `bundle` argument contains a 243-trit bundle hash.
 */
exports.normalizedBundle = function (bundle) {
    if (bundle.length !== exports.HASH_LENGTH) {
        throw new Error(errors.ILLEGAL_BUNDLE_HASH_LENGTH);
    }
    var output = new Int8Array(exports.HASH_LENGTH / converter_1.TRYTE_WIDTH);
    for (var i = 0; i < exports.NUMBER_OF_SECURITY_LEVELS; i++) {
        var sum = 0;
        for (var j = i * exports.NORMALIZED_FRAGMENT_LENGTH; j < (i + 1) * exports.NORMALIZED_FRAGMENT_LENGTH; j++) {
            sum += output[j] =
                bundle[j * converter_1.TRYTE_WIDTH] + bundle[j * converter_1.TRYTE_WIDTH + 1] * 3 + bundle[j * converter_1.TRYTE_WIDTH + 2] * 9;
        }
        if (sum >= 0) {
            while (sum-- > 0) {
                for (var j = i * exports.NORMALIZED_FRAGMENT_LENGTH; j < (i + 1) * exports.NORMALIZED_FRAGMENT_LENGTH; j++) {
                    if (output[j] > exports.MIN_TRYTE_VALUE) {
                        output[j]--;
                        break;
                    }
                }
            }
        }
        else {
            while (sum++ < 0) {
                for (var j = i * exports.NORMALIZED_FRAGMENT_LENGTH; j < (i + 1) * exports.NORMALIZED_FRAGMENT_LENGTH; j++) {
                    if (output[j] < exports.MAX_TRYTE_VALUE) {
                        output[j]++;
                        break;
                    }
                }
            }
        }
    }
    return output;
};
//# sourceMappingURL=signing.js.map