PHP WebShell

Текущая директория: /usr/lib/node_modules/bitgo/node_modules/msrcrypto/dist

Просмотр файла: msrcrypto.mjs

//*******************************************************************************
//
//    Copyright 2018 Microsoft
//
//    Licensed under the Apache License, Version 2.0 (the "License");
//    you may not use this file except in compliance with the License.
//    You may obtain a copy of the License at
//
//        http://www.apache.org/licenses/LICENSE-2.0
//
//    Unless required by applicable law or agreed to in writing, software
//    distributed under the License is distributed on an "AS IS" BASIS,
//    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//    See the License for the specific language governing permissions and
//    limitations under the License.
//
//*******************************************************************************

//*******************************************************************************
//
//    NOTICE
//
//    This file has been modified from the original source:
//      https://www.microsoft.com/en-us/download/details.aspx?id=52439
//
//    To see changes, check the commit history here:
//      https://github.com/kevlened/msrCrypto/commits/master
//
//*******************************************************************************

// Don't enforce entropy in environments without document
var glb = typeof global !== "undefined" ? global :
          typeof window !== "undefined" ? window :
          typeof self !== "undefined" ? self :
          undefined;

if (typeof document === 'undefined' && glb) {
    glb.document = {
        attachEvent: function() {}
    };
}

var msrCryptoVersion = "1.5.6";
var msrCrypto = msrCrypto || (function () {

    "use strict";

var operations = {};

operations.register = function (operationType, algorithmName, functionToCall) {

    if (!operations[operationType]) {
        operations[operationType] = {};
    }

    var op = operations[operationType];

    if (!op[algorithmName]) {
        op[algorithmName] = functionToCall;
    }

};

operations.exists = function (operationType, algorithmName) {
    if (!operations[operationType]) {
        return false;
    }

    return (operations[operationType][algorithmName]) ? true : false;
};

/// Store the URL for this script. We will need this later to instantiate
/// new web workers (if supported).
var scriptUrl = (function () {

    if (typeof document !== "undefined") {
        // Use error.stack to find out the name of this script
        try {
            throw new Error();
        } catch (e) {
            if (e.stack) {
                var match = /\w+:\/\/(.+?\/)*.+\.js/.exec(e.stack);
                return (match && match.length > 0) ? match[0] : null;
            }
        }
    } else if (typeof self !== "undefined") {
        // If this script is being run in a WebWorker, 'document' will not exist
        //  but we can use self.
        return self.location.href;
    }

    // We must be running in an environment without document or self.
    return null;

})();

// Indication if the user provided entropy into the entropy pool.
var fprngEntropyProvided = false;

var isPermanentForceSync = glb && glb.msrCryptoPermanentForceSync;

// Support for webWorkers IE10+.
var webWorkerSupport = (typeof Worker !== "undefined") && !isPermanentForceSync;

// Is this script running in an instance of a webWorker?
var runningInWorkerInstance = (typeof importScripts !== "undefined") && !isPermanentForceSync;

// Typed Arrays support?
var typedArraySupport = (typeof Uint8Array !== "undefined");

// Property setter/getter support IE9+.
var setterSupport = (function () {
    try {
        Object.defineProperty({}, "oncomplete", {});
        return true;
    } catch (ex) {
        return false;
    }
}());

// Run in async mode (requires web workers) and user can override to sync mode
//  by setting the .forceSync property to true on the subtle interface
//  this can be changes 'on the fly'.
var asyncMode = webWorkerSupport;

var createProperty = function (parentObject, propertyName, initialValue, getterFunction, setterFunction) {
    /// <param name="parentObject" type="Object"/>
    /// <param name="propertyName" type="String"/>
    /// <param name="initialValue" type="Object"/>
    /// <param name="getterFunction" type="Function"/>
    /// <param name="setterFunction" type="Function" optional="true"/>

    if (!setterSupport) {
        parentObject[propertyName] = initialValue;
        return;
    }

    var setGet = {};

    getterFunction && (setGet.get = getterFunction);
    setterFunction && (setGet.set = setterFunction);

    Object.defineProperty(
        parentObject,
        propertyName, setGet);
};

// Collection of hash functions for global availability.
// Each hashfunction will add itself to the collection as it is evaluated.
var msrcryptoHashFunctions = {};

var msrcryptoUtilities = (function () {

    var encodingChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";

    var btoaSupport = (typeof btoa !== "undefined");

    function toBase64(data, base64Url) {
        /// <signature>
        ///     <summary>Converts byte data to Base64 string</summary>
        ///     <param name="data" type="Array">An array of bytes values (numbers from 0-255)</param>
        ///     <param name="base64Url" type="Boolean" optional="true">Converts to a Base64Url string if True (default = false)</param>
        ///     <returns type="String" />
        /// </signature>
        /// <signature>
        ///     <summary>Converts byte data to Base64 string</summary>
        ///     <param name="data" type="UInt8Array">A UInt8Array</param>
        ///     <param name="base64Url" type="Boolean" optional="true">Converts to a Base64Url string if True (default = false)</param>
        ///     <returns type="String" />
        /// </signature>
        /// <signature>
        ///     <summary>Converts text to Base64 string</summary>
        ///     <param name="data" type="String">Text string</param>
        ///     <param name="base64Url" type="Boolean" optional="true">Converts to a Base64Url string if True (default = false)</param>
        ///     <returns type="String" />
        /// </signature>

        var output = "";

        if (!base64Url) {
            base64Url = false;
        }

        // If the input is an array type, convert it to a string.
        // The built-in btoa takes strings.
        if (data.pop || data.subarray) {
            data = String.fromCharCode.apply(null, data);
        }

        if (btoaSupport) {
            output = btoa(data);
        } else {

            var char1, char2, char3, enc1, enc2, enc3, enc4;
            var i;

            for (i = 0; i < data.length; i += 3) {

                // Get the next three chars.
                char1 = data.charCodeAt(i);
                char2 = data.charCodeAt(i + 1);
                char3 = data.charCodeAt(i + 2);

                // Encode three bytes over four 6-bit values.
                // [A7,A6,A5,A4,A3,A2,A1,A0][B7,B6,B5,B4,B3,B2,B1,B0][C7,C6,C5,C4,C3,C2,C1,C0].
                // [A7,A6,A5,A4,A3,A2][A1,A0,B7,B6,B5,B4][B3,B2,B1,B0,C7,C6][C5,C4,C3,C2,C1,C0].

                // 'enc1' = high 6-bits from char1
                enc1 = char1 >> 2;
                // 'enc2' = 2 low-bits of char1 + 4 high-bits of char2
                enc2 = ((char1 & 0x3) << 4) | (char2 >> 4);
                // 'enc3' = 4 low-bits of char2 + 2 high-bits of char3
                enc3 = ((char2 & 0xF) << 2) | (char3 >> 6);
                // 'enc4' = 6 low-bits of char3
                enc4 = char3 & 0x3F;

                // 'char2' could be 'nothing' if there is only one char left to encode
                //   if so, set enc3 & enc4 to 64 as padding.
                if (isNaN(char2)) {
                    enc3 = enc4 = 64;

                    // If there was only two chars to encode char3 will be 'nothing'
                    //   set enc4 to 64 as padding.
                } else if (isNaN(char3)) {
                    enc4 = 64;
                }

                // Lookup the base-64 value for each encoding.
                output = output +
                encodingChars.charAt(enc1) +
                encodingChars.charAt(enc2) +
                encodingChars.charAt(enc3) +
                encodingChars.charAt(enc4);
            }
        }

        if (base64Url) {
            return output.replace(/\+/g, "-").replace(/\//g, "_").replace(/\=/g, "");
        }

        return output;
    }

    function base64ToString(encodedString) {
        /// <signature>
        ///     <summary>Converts a Base64/Base64Url string to a text</summary>
        ///     <param name="encodedString" type="String">A Base64/Base64Url encoded string</param>
        ///     <returns type="String" />
        /// </signature>

        if (btoaSupport) {

            // This could be encoded as base64url (different from base64)
            encodedString = encodedString.replace(/-/g, "+").replace(/_/g, "/");

            // In case the padding is missing, add some.
            while (encodedString.length % 4 !== 0) {
                encodedString += "=";
            }

            return atob(encodedString);
        }

        return String.fromCharCode.apply(null, base64ToBytes(encodedString));

    }

    function base64ToBytes(encodedString) {
        /// <signature>
        ///     <summary>Converts a Base64/Base64Url string to an Array</summary>
        ///     <param name="encodedString" type="String">A Base64/Base64Url encoded string</param>
        ///     <returns type="Array" />
        /// </signature>

        // This could be encoded as base64url (different from base64)
        encodedString = encodedString.replace(/-/g, "+").replace(/_/g, "/");

        // In case the padding is missing, add some.
        while (encodedString.length % 4 !== 0) {
            encodedString += "=";
        }

        var output = [];
        var char1, char2, char3;
        var enc1, enc2, enc3, enc4;
        var i;

        // Remove any chars not in the base-64 space.
        encodedString = encodedString.replace(/[^A-Za-z0-9\+\/\=]/g, "");

        for (i = 0; i < encodedString.length; i += 4) {

            // Get 4 characters from the encoded string.
            enc1 = encodingChars.indexOf(encodedString.charAt(i));
            enc2 = encodingChars.indexOf(encodedString.charAt(i + 1));
            enc3 = encodingChars.indexOf(encodedString.charAt(i + 2));
            enc4 = encodingChars.indexOf(encodedString.charAt(i + 3));

            // Convert four 6-bit values to three characters.
            // [A7,A6,A5,A4,A3,A2][A1,A0,B7,B6,B5,B4][B3,B2,B1,B0,C7,C6][C5,C4,C3,C2,C1,C0].
            // [A7,A6,A5,A4,A3,A2,A1,A0][B7,B6,B5,B4,B3,B2,B1,B0][C7,C6,C5,C4,C3,C2,C1,C0].

            // 'char1' = all 6 bits of enc1 + 2 high-bits of enc2.
            char1 = (enc1 << 2) | (enc2 >> 4);
            // 'char2' = 4 low-bits of enc2 + 4 high-bits of enc3.
            char2 = ((enc2 & 15) << 4) | (enc3 >> 2);
            // 'char3' = 2 low-bits of enc3 + all 6 bits of enc4.
            char3 = ((enc3 & 3) << 6) | enc4;

            // Convert char1 to string character and append to output
            output.push(char1);

            // 'enc3' could be padding
            //   if so, 'char2' is ignored.
            if (enc3 !== 64) {
                output.push(char2);
            }

            // 'enc4' could be padding
            //   if so, 'char3' is ignored.
            if (enc4 !== 64) {
                output.push(char3);
            }

        }

        return output;

    }

    function getObjectType(object) {
        /// <signature>
        ///     <summary>Returns the name of an object type</summary>
        ///     <param name="object" type="Object"></param>
        ///     <returns type="String" />
        /// </signature>

        return Object.prototype.toString.call(object).slice(8, -1);
    }

    function bytesToHexString(bytes, separate) {
        /// <signature>
        ///     <summary>Converts an Array of bytes values (0-255) to a Hex string</summary>
        ///     <param name="bytes" type="Array"/>
        ///     <param name="separate" type="Boolean" optional="true">Inserts a separator for display purposes (default = false)</param>
        ///     <returns type="String" />
        /// </signature>

        var result = "";
        if (typeof separate === "undefined") {
            separate = false;
        }

        for (var i = 0; i < bytes.length; i++) {

            if (separate && (i % 4 === 0) && i !== 0) {
                result += "-";
            }

            var hexval = bytes[i].toString(16).toUpperCase();
            // Add a leading zero if needed.
            if (hexval.length === 1) {
                result += "0";
            }

            result += hexval;
        }

        return result;
    }

    function bytesToInt32(bytes, index) {
        /// <summary>
        /// Converts four bytes to a 32-bit int
        /// </summary>
        /// <param name="bytes">The bytes to convert</param>
        /// <param name="index" optional="true">Optional starting point</param>
        /// <returns type="Number">32-bit number</returns>
        index = (index || 0);

        return (bytes[index] << 24) |
               (bytes[index + 1] << 16) |
               (bytes[index + 2] << 8) |
                bytes[index + 3];
    }

    function stringToBytes(messageString) {
        /// <signature>
        ///     <summary>Converts a String to an Array of byte values (0-255)</summary>
        ///     <param name="messageString" type="String"/>
        ///     <returns type="Array" />
        /// </signature>

        var bytes = new Array(messageString.length);

        for (var i = 0; i < bytes.length; i++) {
            bytes[i] = messageString.charCodeAt(i);
        }

        return bytes;
    }

    function hexToBytesArray(hexString) {
        /// <signature>
        ///     <summary>Converts a Hex-String to an Array of byte values (0-255)</summary>
        ///     <param name="hexString" type="String"/>
        ///     <returns type="Array" />
        /// </signature>

        hexString = hexString.replace(/\-/g, "");

        var result = [];
        while (hexString.length >= 2) {
            result.push(parseInt(hexString.substring(0, 2), 16));
            hexString = hexString.substring(2, hexString.length);
        }

        return result;
    }

    function clone(object) {
        /// <signature>
        ///     <summary>Creates a shallow clone of an Object</summary>
        ///     <param name="object" type="Object"/>
        ///     <returns type="Object" />
        /// </signature>

        var newObject = {};
        for (var propertyName in object) {
            if (object.hasOwnProperty(propertyName)) {
                newObject[propertyName] = object[propertyName];
            }
        }
        return newObject;
    }

    function unpackData(base64String, arraySize, toUint32s) {
        /// <signature>
        ///     <summary>Unpacks Base64 encoded data into arrays of data.</summary>
        ///     <param name="base64String" type="String">Base64 encoded data</param>
        ///     <param name="arraySize" type="Number" optional="true">Break data into sub-arrays of a given length</param>
        ///     <param name="toUint32s" type="Boolean" optional="true">Treat data as 32-bit data instead of byte data</param>
        ///     <returns type="Array" />
        /// </signature>

        var bytes = base64ToBytes(base64String),
            data = [],
            i;

        if (isNaN(arraySize)) {
            return bytes;
        } else {
            for (i = 0; i < bytes.length; i += arraySize) {
                data.push(bytes.slice(i, i + arraySize));
            }
        }

        if (toUint32s) {
            for (i = 0; i < data.length; i++) {
                data[i] = (data[i][0] << 24) + (data[i][1] << 16) + (data[i][2] << 8) + data[i][3];
            }
        }

        return data;
    }

    function int32ToBytes(int32) {
        /// <signature>
        ///     <summary>Converts a 32-bit number to an Array of 4 bytes</summary>
        ///     <param name="int32" type="Number">32-bit number</param>
        ///     <returns type="Array" />
        /// </signature>
        return [(int32 >>> 24) & 255, (int32 >>> 16) & 255, (int32 >>> 8) & 255, int32 & 255];
    }

    function int32ArrayToBytes(int32Array) {
        /// <signature>
        ///     <summary>Converts an Array 32-bit numbers to an Array bytes</summary>
        ///     <param name="int32Array" type="Array">Array of 32-bit numbers</param>
        ///     <returns type="Array" />
        /// </signature>

        var result = [];
        for (var i = 0; i < int32Array.length; i++) {
            result = result.concat(int32ToBytes(int32Array[i]));
        }
        return result;
    }

    function xorVectors(a, b) {
        /// <signature>
        ///     <summary>Exclusive OR (XOR) two arrays.</summary>
        ///     <param name="a" type="Array">Input array.</param>
        ///     <param name="b" type="Array">Input array.</param>
        ///     <returns type="Array">XOR of the two arrays. The length is minimum of the two input array lengths.</returns>
        /// </signature>

        var length = Math.min(a.length, b.length),
            res = new Array(length);
        for (var i = 0 ; i < length ; i += 1) {
            res[i] = a[i] ^ b[i];
        }
        return res;
    }

    function getVector(length, fillValue) {
        /// <signature>
        ///     <summary>Get an array filled with zeroes (or optional fillValue.)</summary>
        ///     <param name="length" type="Number">Requested array length.</param>
        ///     <param name="fillValue" type="Number" optional="true"></param>
        ///     <returns type="Array"></returns>
        /// </signature>

        // Use a default value of zero
        fillValue || (fillValue = 0);

        var res = new Array(length);
        for (var i = 0; i < length; i += 1) {
            res[i] = fillValue;
        }
        return res;
    }

    function toArray(typedArray) {
        /// <signature>
        ///     <summary>Converts a UInt8Array to a regular JavaScript Array</summary>
        ///     <param name="typedArray" type="UInt8Array"></param>
        ///     <returns type="Array"></returns>
        /// </signature>

        // If undefined or null return an empty array
        if (!typedArray) {
            return [];
        }

        // If already an Array return it
        if (typedArray.pop) {
            return typedArray;
        }

        // If it's an ArrayBuffer, convert it to a Uint8Array first
        if (typedArray.isView || getObjectType(typedArray) === 'ArrayBuffer') {
            typedArray = new Uint8Array(typedArray);
        }

        // A single element array will cause a new Array to be created with the length
        // equal to the value of the single element. Not what we want.
        // We'll return a new single element array with the single value.
        return (typedArray.length === 1) ? [typedArray[0]] : Array.apply(null, typedArray);
    }

    function padEnd(array, value, finalLength) {
        /// <signature>
        ///     <summary>Pads the end of an array with a specified value</summary>
        ///     <param name="array" type="Array"></param>
        ///     <param name="value" type="Number">The value to pad to the array</param>
        ///     <param name="finalLength" type="Number">The final resulting length with padding</param>
        ///     <returns type="Array"></returns>
        /// </signature>

        while (array.length < finalLength) {
            array.push(value);
        }

        return array;
    }

    function padFront(array, value, finalLength) {
        /// <signature>
        ///     <summary>Pads the front of an array with a specified value</summary>
        ///     <param name="array" type="Array"></param>
        ///     <param name="value" type="Number">The value to pad to the array</param>
        ///     <param name="finalLength" type="Number">The final resulting length with padding</param>
        ///     <returns type="Array"></returns>
        /// </signature>

        while (array.length < finalLength) {
            array.unshift(value);
        }

        return array;
    }

    function arraysEqual(array1, array2) {
        /// <signature>
        ///     <summary>Checks if two Arrays are equal by comparing their values.</summary>
        ///     <param name="array1" type="Array"></param>
        ///     <param name="array2" type="Array"></param>
        ///     <returns type="Array"></returns>
        /// </signature>

        var result = true;

        if (array1.length !== array2.length) {
            result = false;
        }

        for (var i = 0; i < array1.length; i++) {
            if (array1[i] !== array2[i]) {
                result = false;
            }
        }

        return result;
    }

    function verifyByteArray(array) {
        /// <signature>
        ///     <summary>Verify that an Array contains only byte values (0-255)</summary>
        ///     <param name="array" type="Array"></param>
        ///     <returns type="Boolean">Returns true if all values are 0-255</returns>
        /// </signature>

        if (getObjectType(array) !== "Array") {
            return false;
        }

        var element;

        for (var i = 0; i < array.length; i++) {

            element = array[i];

            if (isNaN(element) || element < 0 || element > 255) {
                return false;
            }
        }

        return true;
    }

    return {
        toBase64: toBase64,
        base64ToString: base64ToString,
        base64ToBytes: base64ToBytes,
        getObjectType: getObjectType,
        bytesToHexString: bytesToHexString,
        bytesToInt32: bytesToInt32,
        stringToBytes: stringToBytes,
        unpackData: unpackData,
        hexToBytesArray: hexToBytesArray,
        int32ToBytes: int32ToBytes,
        int32ArrayToBytes: int32ArrayToBytes,
        toArray: toArray,
        arraysEqual: arraysEqual,
        clone: clone,
        xorVectors: xorVectors,
        padEnd: padEnd,
        padFront: padFront,
        getVector: getVector,
        verifyByteArray: verifyByteArray
    };

})();

var msrcryptoWorker = (function () {

    // If we're running in a webworker we need to postMessage to return our result
    //   otherwise just return the value as normal.
    function returnResult(result) {
        if (runningInWorkerInstance) {
            self.postMessage(result);
        }
        return result;
    }

    return {

        jsCryptoRunner: function ( e) {

            var operation = e.data.operationType;
            var result;

            if (!operations.exists(operation, e.data.algorithm.name)) {
                throw new Error("unregistered algorithm.");
            }

            var func = operations[operation][e.data.algorithm.name];

            var p = e.data;

            if (p.operationSubType === "process") {
                func(p);
                result = returnResult({ type: "process" });
            } else {
                result = returnResult(func(p));
            }

            return result;
        }
    };

})();

// If this is running in a webworker we need self.onmessage to receive messages from
//   the calling script.
// If we are in 'synchronous mode' (everything running in one script)
//   we don't want to override self.onmessage.
if (runningInWorkerInstance) {

    self.onmessage = function ( e) {

        // When this worker first gets instantiated we will receive seed data
        //   for this workers prng.
        if (e.data.prngSeed) {
            var entropy = e.data.prngSeed;
            msrcryptoPseudoRandom.init(entropy);
            return;
        }

        // Process the crypto operation
        msrcryptoWorker.jsCryptoRunner(e);
    };
}

var msrcryptoJwk = (function () {

    var utils = msrcryptoUtilities;

    function stringToArray(stringData) {

        var result = [];

        for (var i = 0; i < stringData.length; i++) {
            result[i] = stringData.charCodeAt(i);
        }

        if (result[result.length - 1] === 0) {
            result.pop();
        }

        return result;
    }

    function getKeyType(keyHandle) {

        var algType = keyHandle.algorithm.name.slice(0, 3).toLowerCase();

        if (algType === "rsa") {
            return "RSA";
        }

        if (algType === "ecd") {
            return "EC";
        }

        return "oct";
    }

    var algorithmMap = {

        hmac : function(algorithm) {
                    return "HS" + algorithm.hash.name.substring(algorithm.hash.name.indexOf('-') + 1);
        },

        "aes-cbc" : function(algorithm) {
            return "A" + algorithm.length.toString() + "CBC";
        },

        "aes-gcm" : function(algorithm) {
            return "A" + algorithm.length.toString() + "GCM";
        },

        "rsaes-pkcs1-v1_5": function (algorithm) {

            return "RSA1_5";
        },

        "rsassa-pkcs1-v1_5": function (algorithm) {

           return "RS" + algorithm.hash.name.substring(algorithm.hash.name.indexOf('-') + 1);

        },

        "rsa-oaep": function (algorithm) {

            return "RS-OAEP-" + algorithm.hash.name.substring(algorithm.hash.name.indexOf('-') + 1);
        },

        "rsa-pss": function (algorithm) {

            return "PS" + algorithm.hash.name.substring(algorithm.hash.name.indexOf('-') + 1);
        },

        "ecdsa": function (algorithm) {

            return "EC-" + algorithm.namedCurve.substring(algorithm.namedCurve.indexOf('-') + 1);
        },

        "ecdh": function (algorithm) {

            return "EC-" + algorithm.namedCurve.substring(algorithm.namedCurve.indexOf('-') + 1);
        }
    };

    function keyToJwk(keyHandle, keyData) {

        var key = {};

        key.kty = getKeyType(keyHandle);
        key.ext = keyHandle.extractable;
        key.alg = algorithmMap[keyHandle.algorithm.name.toLowerCase()](keyHandle.algorithm);
        key.key_ops = keyHandle.keyUsage;

        // Using .pop to determine if a property value is an array.
        if (keyData.pop) {
            key.k = utils.toBase64(keyData, true);
        } else {
            // Convert the base64Url properties to byte arrays
            for (var property in keyData) {
                if (keyData[property].pop) {
                    key[property] = utils.toBase64(keyData[property], true);
                }
            }
        }

        if (keyHandle.algorithm.namedCurve) {
            key["crv"] = keyHandle.algorithm.namedCurve;
        }

        return key;

    }

    function keyToJwkOld(keyHandle, keyData) {

        var key = {};

        key.kty = getKeyType(keyHandle);
        key.extractable = keyHandle.extractable;

        // Using .pop to determine if a property value is an array.
        if (keyData.pop) {
            key.k = utils.toBase64(keyData, true);
        } else {
            // Convert the base64Url properties to byte arrays
            for (var property in keyData) {
                if (keyData[property].pop) {
                    key[property] = utils.toBase64(keyData[property], true);
                }
            }
        }

        if (keyHandle.algorithm.namedCurve) {
            key["crv"] = keyHandle.algorithm.namedCurve;
        }

        var stringData = JSON.stringify(key, null, '\t');

        return stringToArray(stringData);

    }

    // 'jwkKeyData' is an array of bytes. Each byte is a charCode for a json key string
    function jwkToKey(keyData, algorithm, propsToArray) {

        // Convert the json string to an object
        var jsonKeyObject = JSON.parse(JSON.stringify(keyData)); //JSON.parse(jsonString);

        // Convert the base64url encoded properties to byte arrays
        for (var i = 0; i < propsToArray.length; i += 1) {
            var propValue = jsonKeyObject[propsToArray[i]];
            if (propValue) {
                jsonKeyObject[propsToArray[i]] =
                   utils.base64ToBytes(propValue);
            }
        }

        return jsonKeyObject;
    }

    return {
        keyToJwkOld: keyToJwkOld,
        keyToJwk: keyToJwk,
        jwkToKey: jwkToKey
    };
})();

function msrcryptoMath() {
    // 'number' of bits per digit. Must be even.
    var DIGIT_BITS = 24;
    // 'number' of bytes per digit.
    var DIGIT_NUM_BYTES = Math.floor(DIGIT_BITS / 8);
    // digit mask.
    var DIGIT_MASK = (1 << DIGIT_BITS) - 1;
    // digit base.
    var DIGIT_BASE = (1 << DIGIT_BITS);
    // max digit value, unsigned
    var DIGIT_MAX = DIGIT_MASK;

    // Construct scaler for DIGIT_NUM_BYTES, so I don't have to multiply in the loop
    var DIGIT_SCALER = [1, 256];
    for (var i = 2; i <= DIGIT_NUM_BYTES; i++) {
        DIGIT_SCALER[i] = DIGIT_SCALER[i - 1] * 256;
    }

    // Number of trailing zero bits in numbers 0..15 (4 bits). [0] is for 0, [15] is for 15.
    var Zero = [0];
    var One = [1];

    // Create an array, mimics the constructors for typed arrays.
    function createArray( parameter) {
        var i, array = null;
        if (!arguments.length || typeof arguments[0] === "number") {
            // A number.
            array = new Array(parameter);
            for (i = 0; i < parameter; i += 1) {
                array[i] = 0;
            }
        } else if (typeof arguments[0] === "object") {
            // An array or other index-able object
            array = new Array(parameter.length);
            for (i = 0; i < parameter.length; i += 1) {
                array[i] = parameter[i];
            }
        }
        return array;
    }

    function swapEndianness(bytes) {
        /// <summary>Swap big endian bytes to little endian bytes.</summary>
        /// <param name="bytes" type="Bytes">UInt8Array - representing a big-integer.</param>
        /// <returns type="Bytes">UInt8Array - the number with endianness swapped.</returns>

        var out = new Array(bytes.length);
        var i = 0;

        while (i < bytes.length) {
            out[i] = bytes[bytes.length - i - 1];
            i += 1;
        }

        return out;
    }

    function stringToDigits(number, radix) {
        /// <summary>Parse a String in a given base into a little endian digit array.</summary>
        /// <param name="number" type="String">Input unsigned integer in a string.</param>
        /// <param name="radix" optional="true" integer="true">
        /// <![CDATA[ Radix of the input. Must be >=2 and <=36. Default = 10. ]]>
        /// </param>
        /// <returns type="Array">Array of digits in little endian; [0] is LSW.</returns>

        // skip leading and trailing whitespace.
        number = number.replace(/^\s+|\s+$/g, '');
        var num = [0];
        var buffer = [0];
        radix = radix || 10;        // default radix is 10
        for (var i = 0; i < number.length; i += 1) {
            // Extract character
            var char = parseInt(number[i], radix);
            if (isNaN(char)) {
                throw new Error("Failed to convert string to integer in radix " + radix.toString());
            }

            // 'buffer' = 'num' * 'radix'
            multiply(num, radix, buffer);

            // 'num' = 'buffer' + 'char'
            add(buffer, [ char], num);
            normalizeDigitArray(num);
        }

        return num;
    }

    function digitsToString(digits, radix) {
        /// <summary>Convert a big-endian byte array to a number in string in radix.</summary>
        /// <param name="digits" type="Digits">A big integer as a little-endian digit array.</param>
        /// <param name="radix" optional="true" integer="true">Radix from 2 to 26. Default = 10.</param>
        /// <returns type="String">The number in base radix as a string.</returns>

        radix = radix || 10;
        if (DIGIT_BASE <= radix) {
            throw new Error("DIGIT_BASE is smaller than RADIX; cannot convert.");
        }

        var wordLength = digits.length;
        var quotient = [];
        var remainder = [];
        var temp1 = [];
        var temp2 = [];
        var divisor = [];
        var a = [];
        var i;

        // Find the largest divisor that fits in a digit in radix
        //divisor[0] = 10000; // Largest power of ten fitting in a digit
        var sb = "";
        var pad = "0";
        divisor[0] = radix;
        while (Math.floor(DIGIT_BASE / divisor[0]) >= radix) {
            divisor[0] = divisor[0] * radix;
            pad = pad.concat("0");
        }

        for (i = 0; i < wordLength; i += 1) {
            a[i] = digits[i];
        }

        do {
            var allZeros = true;
            for (i = 0; i < a.length; i += 1) {
                if (a[i] !== 0) {
                    allZeros = false;
                    break;
                }
            }

            if (allZeros) {
                break;
            }

            divRem(a, divisor, quotient, remainder, temp1, temp2);
            normalizeDigitArray(quotient, a.length, true);

            var newDigits = remainder[0].toString(radix);
            sb = pad.substring(0, pad.length - newDigits.length) + newDigits + sb;

            var swap = a;
            a = quotient;
            quotient = swap;
        } while (true);

        // Trim leading zeros
        while (sb.length !== 0 && sb[0] === "0") {
            sb = sb.substring(1, sb.length);
        }

        if (sb.length === 0) {
            sb = "0";
        }

        return sb;
    }

    function powerOfTwo(i) {
        /// <summary>Given a positive integer i, return a big integer in big-endian format
        ///   equal to 2^i. This is useful for creating fields of certain size.</summary>
        /// <param name="i" type="Number">A positive integer.</param>
        /// <returns>UInt8Array - 2^i as a big-endian byte array.</returns>
        var requiredBytes = Math.ceil((i + 1) / 8);
        var out = createArray(requiredBytes);
        out[0] = Math.pow(2, i % 8);
        return out;
    }

    function computeBitArray(bytes) {
        /// <summary>Given an array of bytes in big-endian format, compute UInt8Array with
        /// one element for each bit (0 or 1), in little-endian order.</summary>
        /// <param name="bytes" type="Digits">An array of bytes in big-endian format.</param>
        /// <returns type="Digits">An array of 0's and 1's representing the bits in little-endian.</returns>

        var out = createArray(bytes.length * 8);
        var bitLength = 0;
        var i = bytes.length - 1;
        while (i >= 0) {
            var j = 0;
            while (j < 8) {
                var mask = (1 << j);
                var bit = ((bytes[i] & mask) === mask) ? 1 : 0;
                var thisBitIndex = (8 * ((bytes.length - i) - 1)) + j;

                if (bit === 1) {
                    bitLength = thisBitIndex + 1;
                }

                out[thisBitIndex] = bit;
                j += 1;
            }

            i--;
        }

        return out.slice(0, bitLength);
    }

    function bitScanForward(value) {
        /// <summary>Return the 0-based index of the first non-zero bit starting at the most significant bit position.</summary>
        /// <param name="value" type="Number" integer="true">Value to scan.</param>
        /// <returns>Zero-based index of the first non-zero bit.</returns>
        var mask = DIGIT_BASE >>> 1;
        var index = DIGIT_BITS;
        while (index-- > 0) {
            if ((value & mask) === mask) {
                break;
            }
            mask = mask >>> 1;
        }

        return index;
    }

    function highestSetBit(bytes) {
        /// <summary>Returns the (1 indexed) index of the highest set bit.</summary>
        /// <param name="bytes" type="Array">A big-endian big integer byte array.</param>
        /// <returns type="Number">The index of the highest bit.</returns>

        var i = 0;
        var bitLength = 0;

        while (i < bytes.length) {
            if (bitLength === 0) {
                // Look for highest set bit in this byte
                var j = 7;
                while (j >= 0 && bitLength === 0) {
                    var mask = (1 << j);
                    if ((bytes[i] & mask) === mask) {
                        bitLength = j + 1;
                    }

                    j--;
                }
            } else {
                bitLength += 8;
            }

            i += 1;
        }

        return bitLength;
    }

    function fixedWindowRecode(digits, windowSize, t) {
        /// <summary></summary>
        /// <param name="digits" type="Array">Digits to recode</param>
        /// <param name="windowSize" type="Number">Window size</param>
        /// <returns type="Array">Recoded digits</returns>}

        // Make a copy of digits because we are going to modify it with shifts below.
        digits = digits.slice();

        var recodedDigits = [],
            windowSizeBits = Math.pow(2, windowSize),
            windowSizeMinus1Bits = Math.pow(2, windowSize - 1);

        for (var i = 0; i < t; i++) {

            // k_digits[i] := (Z!k mod 2^w) - 2^(w-1);
            recodedDigits[i] = (digits[0] % windowSizeBits) - windowSizeMinus1Bits;

            // k := (k - k_digits[i])/2^(w-1);
            digits[0] = digits[0] - recodedDigits[i];

            // PERF : can probably do this faster
            cryptoMath.shiftRight(digits, digits, windowSize - 1);
        }

        recodedDigits[i] = digits[0];

        return recodedDigits;
    }

    function fetchBits(digits, startBit, count) {
        /// <summary>From an array of digits, return a sequential set of bits.</summary>
        /// <param name="digits" type=""></param>
        /// <param name="start" type=""></param>
        /// <param name="end" type=""></param>

        var startDigit = Math.floor(startBit / cryptoMath.DIGIT_BITS);
        var endDigit = startDigit + 1;

        var shiftRight = (startBit % cryptoMath.DIGIT_BITS);
        var shiftLeft = cryptoMath.DIGIT_BITS - shiftRight;

        var bits = (digits[startDigit] >>> shiftRight) | (digits[endDigit] << shiftLeft);

        return bits & (cryptoMath.DIGIT_MASK >>> (cryptoMath.DIGIT_BITS - count));

    }

    function fetchBits2(digits, startBit, count) {
        /// <summary>From an array of digits, return a sub-set of bits from an arbitray index.</summary>
        /// <param name="digits" type="Array">Array of digits</param>
        /// <param name="startBit" type="Number">Zero-index position of start bit</param>
        /// <param name="count" type="Number">Number of bits to return</param>
        /// <returns type="Number">Value of n-bits</returns>

        var startDigit = Math.floor(startBit / DIGIT_BITS),
            shiftRight = (startBit % DIGIT_BITS);

        return (digits[startDigit] >>> shiftRight) |
            (digits[startDigit + 1] << (DIGIT_BITS - shiftRight))
            & (DIGIT_MASK >>> (DIGIT_BITS - count));
    }

    function copyArray( source, sourceIndex, destination, destIndex, length) {
        /// <summary>Copies a range of elements from one array to another array.</summary>
        /// <param name="source" type="Array">Source array to copy from.</param>
        /// <param name="sourceIndex" type="Number">The index in the source array at which copying begins.</param>
        /// <param name="destination" type="Array">The array that receives the data.</param>
        /// <param name="destIndex" type="Number">The index in the destination array at which storing begins.</param>
        /// <param name="length" type="Number">The number of elements to copy.</param>
        while (length-- > 0) {
            destination[destIndex + length] = source[sourceIndex + length];
        }
    }

    function isZero(array) {
        /// <summary>Check if an array is zero. All elements are zero.</summary>
        /// <param name="array" type="Digits">UInt16Array - An array to be checked.</param>
        /// <returns type="Boolean"/>
        var i;
        for (i = 0; i < array.length; i += 1) {
            if (array[i] !== 0) {
                return false;
            }
        }
        return true;
    }

    function isEven(array) {
        /// <summary>Returns true if this number is even.</summary>
        /// <param name="array" type="Digits"/>
        /// <returns type="Boolean"/>
        return (array[0] & 0x1) === 0x0;
    }

    function sequenceEqual(left, right) {
        /// <summary>Compare two indexable collections for sequence equality.</summary>
        /// <param name="left" type="Digits">The left array.</param>
        /// <param name="right" type="Digits">The right array.</param>
        /// <returns type="Boolean">True if both arrays are the same.</returns>
        if (left.length !== right.length) {
            return false;
        }

        for (var i = 0; i < left.length; i += 1) {
            if (left[i] !== right[i]) {
                return false;
            }
        }

        return true;
    }

    function bytesToDigits(bytes) {
        /// <summary>Convert an unsigned number from big-endian bytes to little endian digits.</summary>
        /// <param name="bytes" type="Bytes">The number in unsigned big-endian byte format.</param>
        /// <returns type="Array">The digits in little-endian.</returns>

        // Construct scaler for DIGIT_NUM_BYTES, so I don't have to multiply in the loop
        var arrayLength = Math.floor((bytes.length + DIGIT_NUM_BYTES - 1) / DIGIT_NUM_BYTES);
        var array = new Array(arrayLength);
        array[0] = 0;
        var digit = 0, index = 0, scIndex = 0;
        for (var i = bytes.length - 1; i >= 0; i--) {
            digit = digit + (DIGIT_SCALER[scIndex++] * (bytes[i] & 0x0ff));
            if (DIGIT_SCALER[scIndex] === DIGIT_BASE) {
                scIndex = 0;
                array[index++] = digit;
                digit = 0;
            }
        }

        // Last digit (MSW), if there is a need
        if (digit !== 0) {
            array[index] = digit;
        }

        // Replace potential undefined elements with zeros
        while (array[--arrayLength] == null) array[arrayLength] = 0;

        return array;
    }

    function digitsToBytes(digits, trim, minTrimLength) {
        /// <summary>Construct a big endian array of bytes from a litte-endian array of digits.
        /// Always returns at least one byte and trims leading zeros.</summary>
        /// <param name="digits" type="Array">The digits in little-endian.</param>
        /// <param name="trim" type="Boolean" optional="true">Remove the leading zeros from the result (default true)</param>
        /// <param name="minTrimLength" type="Number" optional="true">Minimum length to trim down to. Valid only if trim is true. Default=1.</param>
        /// <returns type="Array">Encoded bytes in big-endian format.</returns>

        var i, j, byte1;
        var bytes = [0];

        if (typeof trim === "undefined") {
            trim = true;
        }

        for (i = 0; i < digits.length; i += 1) {
            byte1 = digits[i];
            for (j = 0; j < DIGIT_NUM_BYTES; j += 1) {
                bytes[i * DIGIT_NUM_BYTES + j] = byte1 & 0x0FF;
                byte1 = Math.floor(byte1 / 256);
            }
        }

        bytes = swapEndianness(bytes);

        if (minTrimLength === undefined) {
            minTrimLength = 1;
        }
        if (trim) {
            while (bytes.length > minTrimLength && bytes[0] === 0) {
                bytes.shift();
            }
        }

        return bytes;
    }

    function intToDigits(value, numDigits) {
        /// <summary>Construct an array of digits from a positive integer.</summary>
        /// <param name="value" type="Number" integer="true">A positive integer to be converted to digit form.</param>
        /// <param name="numDigits" type="Number" optional="true" integer="true">The number of digits to use for the digit form.</param>
        /// <returns type="Array">The given integer in digit form.</returns>

        if (typeof numDigits === "undefined") {
            if (value <= 1) {
                numDigits = 1; // Special case <= 1
            } else {
                var numBits = Math.log(value) / Math.LN2;
                numDigits = Math.ceil(numBits / DIGIT_BITS);
            }
        }

        var digitRepresentation = [];
        while (value > 0) {
            digitRepresentation.push(value % DIGIT_BASE);
            value = Math.floor(value / DIGIT_BASE);
        }

        while (digitRepresentation.length < numDigits) {
            digitRepresentation.push(0);
        }

        return digitRepresentation;
    }

    function mswIndex(digits) {
        /// <summary>Return the index of the most significant word of x, 0-indexed.
        /// If x is zero (no significant index), then -1 is returned.</summary>
        /// <param name="digits" type="Array">Digit array.</param>
        /// <returns type="Number">Index of the most significant word, or -1 if digits is zero.</returns>
        for (var i = digits.length - 1; i >= 0; i--) {
            if (digits[i] !== undefined && digits[i] !== 0) {
                return i;
            }
        }

        return (digits[0] === 0) ? -1 : 0;
    }

    function compareDigits(left, right) {
        /// <summary>Compare two digit arrays by value. Returns an integer indicating the comparison result.
        /// Digit arrays are in little endian.</summary>
        /// <param name="left" type="Array">The object on the left side of the comparison.</param>
        /// <param name="right" type="Array">The object on the right side of the comparison.</param>
        /// <returns type="Number">A value that indicates the relative order of the objects
        ///                           being compared. The value is 0 if the items are equal,
        ///                           negative if the left object precedes the right object,
        ///                           and positive otherwise.</returns>

        var comparisonResult = 0;
        var nLeft = mswIndex(left) + 1;
        var nRight = mswIndex(right) + 1;
        if (nLeft > nRight) {
            comparisonResult = 1;
        } else if (nRight > nLeft) {
            comparisonResult = -1;
        } else {
            while ((nLeft-- > 0) && (comparisonResult === 0)) {
                comparisonResult = left[nLeft] - right[nLeft];
            }
        }

        return comparisonResult;
    }

    function normalizeDigitArray(digits, length, pad) {
        /// <summary>Normalize a digit array by truncating any leading zeroes and adjusting its length.
        /// Set the length if given, and pad it with zeroes to that length of padding is requested.</summary>
        /// <remarks>Normalization results with a zero-indexed length of the array such that the MSW is not zero.
        /// If the final array length is zero and no non-zero digits are found, assign digits[0]=0 and set length to 1.
        /// Optionally, pad with zeroes to the given length, and set the array length.</remarks>
        /// <param name="digits" type="Array">Digit array.</param>
        /// <param name="length" type="Number" integer="true" optional="true">Output length to pad with zeroes.</param>
        /// <param name="pad" type="Boolean" optional="true">Pad with zeroes to length if true [false].</param>
        /// <returns type="Array">Resized digits array; same input object.</returns>

        // Trim. Find the trimmed length and the position to start padding from (if padding is requested).
        var i = mswIndex(digits);

        // set the length to the given length (if given) or the trimmed length
        digits.length = length || i + 1;

        // Pad to the length
        if (pad) {
            while (++i < digits.length) {
                digits[i] = 0;
            }
        }

        if (digits.length <= 0) {
            // no non-zero digits found.
            digits[0] = 0;
            digits.length = 1;
        }

        return digits;
    }

    function shiftRight(source, destination, bits, length) {
        /// <summary>Shift a big integer to the right by the given number of bits or 1 if bits is not specified,
        /// effectively dividing by two (or 2^bits) and ignoring the remainder.</summary>
        /// <param name="source" type="Array">Source digit array.</param>
        /// <param name="destination" type="Array">Destination digit array. May be the same as source.</param>
        /// <param name="bits" integer="true" optional="true">Number of bits to shift, must be less than DIGIT_BITS and greater or equal to zero. Default is 1.</param>
        /// <param name="length" optional="true" integer="true">Number of items to shift from he source array. Default is source.length.</param>
        /// <remarks>This is a numerical shift. Integers are stored in arrays in little-endian format.
        /// Thus, this function shifts an array from higher order indices into lower indices. [0] is LSW.
        /// </remarks>

        if (bits === undefined) {
            bits = 1;
        } else if (bits >= DIGIT_BITS || bits < 0) {
            throw new Error("Invalid bit count for shiftRight");
        }
        if (length === undefined) {
            length = source.length;
        }

        var n = length - 1;
        var leftShiftBitCount = DIGIT_BITS - bits;
        for (var i = 0; i < n; i++) {
            destination[i] = ((source[i + 1] << leftShiftBitCount) | (source[i] >>> bits)) & DIGIT_MASK;
            //a[i] = high|low = low bits of a[i+1] | high bits of a[i]
        }

        destination[n] = source[n] >>> bits;
    }

    function shiftLeft(source, destination, bits, length) {
        /// <summary>Shift a number array to the left by given bits, i.e., multiply by 2^bits.</summary>
        /// <param name="source" type="Array">Source digit array.</param>
        /// <param name="destination" type="Array">Destination digit array. May be the same as source.</param>
        /// <param name="bits" integer="true" optional="true">Number of bits to shift, must be less than DIGIT_BITS and greater or equal to zero. Default is 1.</param>
        /// <param name="length" optional="true" integer="true">Number of items to shift from he source array. Default is source.length.</param>
        /// <remarks>An additional MSW digit may be added if the leftshift out from the current MSW produces a non-zero result. [0] is LSW.</remarks>

        if (bits === undefined) {
            bits = 1;
        } else if (bits >= DIGIT_BITS || bits < 0) {
            throw new Error("bit count must be smaller than DIGIT_BITS and positive in shiftLeft");
        }
        if (length === undefined) {
            length = source.length;
        }

        var rightShiftBitCount = DIGIT_BITS - bits;
        // The following line is correct. destination should remain undefined if there are no bits going into it.
        destination[length] = (source[length - 1] >>> (DIGIT_BITS - bits)) || destination[length];
        for (var i = length - 1; i > 0; i--) {
            destination[i] = ((source[i] << bits) | ((source[i - 1] >>> rightShiftBitCount))) & DIGIT_MASK;
            // a[i] = high|low = low bits of a[i] | high bits of a[i-1]
        }

        destination[0] = (source[0] << bits) & DIGIT_MASK;
    }

    //// //// //// //// //// //// //// //// //// //// //// //// //// /
    // Low level math routines
    //// //// //// //// //// //// //// //// //// //// //// //// //// /

    function add(addend1, addend2, sum) {
        /// <summary>Add two arrays of digits into a third array: sum = addend1 + addend2. Carry is recorded in the output if there is one.</summary>
        /// <param name="addend1" type="Array">The first addend.</param>
        /// <param name="addend2" type="Array">The second added.</param>
        /// <param name="sum" type="Array">The output sum buffer addend1 + addend2.</param>
        /// <returns type="Number" integer="true">If carry out then 1, otherwise 0.</returns>

        // Determine which is shorter
        var shortArray = addend1;
        var longArray = addend2;
        if (addend2.length < addend1.length) {
            shortArray = addend2;
            longArray = addend1;
        }

        // Perform the addition
        var s = shortArray.length;
        var carry = 0;
        var i;

        for (i = 0; i < s; i += 1) {
            carry += shortArray[i] + longArray[i];
            sum[i] = carry & DIGIT_MASK;
            carry = (carry >> DIGIT_BITS);
        }

        for (i = s; i < longArray.length; i += 1) {
            carry += longArray[i];
            sum[i] = carry & DIGIT_MASK;
            carry = (carry >> DIGIT_BITS);
        }

        // Set output length
        sum.length = longArray.length;

        // Is there a carry into the next digit?
        if (carry !== 0) {
            sum[i] = carry & DIGIT_MASK;
        }

        return carry;
    }

    function subtract(minuend, subtrahend, difference) {
        /// <summary>Subtraction: difference = minuend - subtrahend. Condition: minuend.length &lt;= subtrahend.length.</summary>
        /// <param name="minuend" type="Array">Minuend.</param>
        /// <param name="subtrahend" type="Array">Subtrahend.</param>
        /// <param name="difference" type="Array">The difference.</param>
        /// <returns type="Number" integer="true">Returns -1 if there is a borrow (minuend &lt; subtrahend), or 0 if there isn't (minuend &gt;= subtrahend).</returns>

        var s = subtrahend.length;
        if (minuend.length < subtrahend.length) {
            s = mswIndex(subtrahend) + 1;
            if (minuend.length < s) {
                throw new Error("Subtrahend is longer than minuend, not supported.");
            }
        }
        var i, carry = 0;
        for (i = 0; i < s; i += 1) {
            carry += minuend[i] - subtrahend[i];
            difference[i] = carry & DIGIT_MASK;
            carry = carry >> DIGIT_BITS;
        }

        // Propagate the carry by subtracting from minuend into difference
        while (i < minuend.length) {
            carry += minuend[i];
            difference[i++] = carry & DIGIT_MASK;
            carry = carry >> DIGIT_BITS;
        }

        return carry;
    }

    function multiply(multiplicant, multiplier, product) {
        /// <summary>Multiply two arrays of digits into a third array using schoolbook.</summary>
        /// <param name="multiplicant" type="Array">Multiplicand.</param>
        /// <param name="multiplier">Multiplier.</param>
        /// <param name="product" type="Array">Product = multiplicant * multiplier.</param>
        /// <returns type="Array">The result buffer; same as the product argument.</returns>

        // Single number or an array?
        var mplr = (typeof multiplier === "number") ? [multiplier] : multiplier;
        var s = Math.max(multiplicant.length, mplr.length);
        var i, j, u;

        // P <- 0
        // We only have to do this for half of result
        //   since the upper half is over-written on the first i iteration.
        for (i = 0; i < s; i += 1) {
            product[i] = 0;
        }

        // For i from 0 by 1 to s - 1 do
        for (i = 0; i < mplr.length; i += 1) {

            // 'u <- 0'
            u = 0;

            // For j from 0 by 1 to s - 1 do
            for (j = 0; j < multiplicant.length; j += 1) {

                // '(u,v) <- a_j * b_i + p_(i+j) + u'
                u += multiplicant[j] * mplr[i] + product[i + j];

                // 'p_(i+j) <- v'
                product[i + j] = (u & DIGIT_MASK) /* v */;
                u = Math.floor(u / DIGIT_BASE); // 'v <- u, u <- 0'
            }

            product[multiplicant.length + i] = (u & DIGIT_MASK);
        }

        // set product length; there may still be leading zero digits after this
        product.length = multiplicant.length + mplr.length;

        return product;
    }

    function divRem(dividend, divisor, quotient, remainder, temp1, temp2) {
        /// <summary>Computes the quotient q and remainder r when dividend is divided by
        ///   divisor.</summary>
        /// <param name="dividend" type="Array">The dividend.</param>
        /// <param name="divisor" type="Array">The divisor.</param>
        /// <param name="quotient" type="Array">Receives the quotient (n digits).</param>
        /// <param name="remainder" type="Array">Receives the remainder (n digits).</param>
        /// <param name="temp1" type="Array" optional="true">Temporary storage (n digits).</param>
        /// <param name="temp2" type="Array" optional="true">Temporary storage (n digits).</param>
        /// <remarks>This is an implementation of Figure 9-1 is Knuth's Algorithm D [Knu2 sec. 4.3.1].
        /// Throws error on division by zero.
        /// </remarks>
        var m = mswIndex(dividend) + 1;        // zero-based length
        var n = mswIndex(divisor) + 1;        // zero-based length
        var qhat, rhat, carry, p, t, i, j;

        // Check for quick results and clear out conditionals
        if (m < n) {
            // dividend < divisor. q=0, remainder=dividend
            copyArray(dividend, 0, remainder, 0, dividend.length);
            remainder.length = dividend.length;
            normalizeDigitArray(remainder);
            quotient[0] = 0;
            quotient.length = 1;
            return;
        } else if (n === 0 || (n === 1 && divisor[n - 1] === 0)) { // self-explanatory
            throw new Error("Division by zero.");
        } else if (n === 1) {
            // divisor is single digit; do a simpler division
            t = divisor[0];
            rhat = 0;
            for (j = m - 1; j >= 0; j--) {
                p = (rhat * DIGIT_BASE) + dividend[j];
                quotient[j] = (p / t) & DIGIT_MASK;
                rhat = (p - quotient[j] * t) & DIGIT_MASK;
            }
            quotient.length = m;
            normalizeDigitArray(quotient);
            remainder[0] = rhat;
            remainder.length = 1;
            return;
        }

        // Normalization step. Align dividend and divisor so that their
        // most significant digits are at the same index.
        // Shift divisor by so many bits (0..DIGIT_BITS-1) to make MSB non-zero.
        var s = DIGIT_BITS - 1 - bitScanForward(divisor[n - 1]);
        var vn = temp1 || [];
        vn.length = n;
        shiftLeft(divisor, vn, s, n);

        var un = temp2 || [];
        un.length = m;
        shiftLeft(dividend, un, s, m);
        un[m] = un[m] || 0;     // must not be undefined

        // Main division loop with quotient estimate qhat
        quotient.length = m - n + 1;
        remainder.length = n;
        for (j = m - n; j >= 0; j--) {
            // Estimate quotient qhat using two-digit by one-digit division
            // because 3-digit by 2-digit division is more complex. Then, correct qhat after this.
            qhat = Math.floor((un[j + n] * DIGIT_BASE + un[j + n - 1]) / vn[n - 1]);
            rhat = (un[j + n] * DIGIT_BASE + un[j + n - 1]) - qhat * vn[n - 1];

            // If the quotient estimate is large, reduce the quotient estimate till the following is satisfied:
            //      qhat = {un[j+n, j+n-1, j+n-2]} div {uv[n-1,n-2]}
            while (true) {
                if (qhat >= DIGIT_BASE || (qhat * vn[n - 2]) > ((rhat * DIGIT_BASE) + un[j + n - 2])) {
                    qhat = qhat - 1;
                    rhat = rhat + vn[n - 1];
                    if (rhat < DIGIT_BASE) {
                        continue;
                    }
                }

                break;
            }

            // Multiply the [shifted] divisor by the quotient estimate and subtract the product from the dividend
            // un = un - qhat*vn
            carry = 0;
            for (i = 0; i < n; i++) {
                p = qhat * vn[i];
                t = un[i + j] - carry - (p & DIGIT_MASK);
                un[i + j] = t & DIGIT_MASK;
                //carry = (p >>> DIGIT_BITS) - (t >> DIGIT_BITS);
                // Don't shift: integer shifts are defined over 32-bit numbers in JS.
                carry = Math.floor(p / DIGIT_BASE) - Math.floor(t / DIGIT_BASE);
            }

            t = un[j + n] - carry;
            un[j + n] = t & DIGIT_MASK;

            // Store the estimated quotient digit (may need correction)
            quotient[j] = qhat & DIGIT_MASK;

            // Correction needed?
            if (t < 0) {
                // quotient too big (at most by 1 divisor)
                // decrement the quotient, and add [shifted] divisor back to the running dividend (remainder)
                quotient[j] = quotient[j] - 1;

                // un = un + vn
                carry = 0;
                for (i = 0; i < n; i++) {
                    t = un[i + j] + vn[i] + carry;
                    un[i + j] = t & DIGIT_MASK;
                    carry = t >> DIGIT_BITS;
                }
                un[j + n] = (un[j + n] + carry) & DIGIT_MASK;
            }
        }

        // De-normalize the remainder (shift right by s bits).
        for (i = 0; i < n; i++) {
            remainder[i] = ((un[i] >>> s) | (un[i + 1] << (DIGIT_BITS - s))) & DIGIT_MASK;
        }

        // Compute correct lengths for the quotient and remainder
        normalizeDigitArray(quotient);
        normalizeDigitArray(remainder);
    }

    function reduce(number, modulus, remainder, temp1, temp2) {
        /// <summary>Integer reduction by a modulus to compute number mod modulus. This function uses division,
        /// and should not be used for repetitive operations.</summary>
        /// <param name="number" type="Array">Input number to reduce.</param>
        /// <param name="modulus" type="Array">Modulus to reduce the input by.</param>
        /// <param name="remainder" type="Array">Output remainder = number mod modulus.</param>
        /// <param name="temp1" type="Array" optional="true">Temporary space, optional.</param>
        /// <param name="temp2" type="Array" optional="true">Temporary space, optional.</param>
        /// <returns type="Array">The resulting remainder is in 0..modulus-1; same as "remainder".</returns>

        // TODO: More efficient reduction implementation
        var quotient = [];
        divRem(number, modulus, quotient, remainder, temp1, temp2);

        return remainder;
    }

    function modMul(multiplicant, multiplier, modulus, product, temp1, temp2) {
        /// <summary>Moduler multiplication of two numbers for a modulus. This function uses multiply and divide method,
        /// and should not be used for repetitive operations.
        /// product can be same as multiplicant and multiplier.</summary>
        /// <param name="multiplicant" type="Array">Multiplicand.</param>
        /// <param name="multiplier">Multiplier.</param>
        /// <param name="modulus" type="Array">Modulus to reduce the product.</param>
        /// <param name="product" type="Array">Output product = multiplicant * multiplier mod modulus.</param>
        /// <param name="temp1" type="Array" optional="true">Scratch space (optional).</param>
        /// <param name="temp2" type="Array" optional="true">Scratch space (optional).</param>
        /// <returns type="Array">The resulting product in in 0..modulus-1; same as product.</returns>

        var quotient = [];
        multiply(multiplicant, multiplier, quotient);
        divRem(quotient, modulus, quotient, product, temp1, temp2);

        return product;
    }

    function eea(a, b, upp, vpp, rpp) {
        /// <summary>Extended Euclidean Algorithm, Berlekamp's version. On return
        /// b*upp - a*vpp = (-1)(k-1)*rpp.</summary>
        /// <param name="a" type="Array">The first number a.</param>
        /// <param name="b" type="Array">The second number b.</param>
        /// <param name="upp" type="Array">a^-1 mod b if gcd=1. Optional.</param>
        /// <param name="vpp" type="Array">b^-1 mod a if gcd=1. Optional./</param>
        /// <param name="rpp" type="Array">gcd(a,b).</param>
        /// <returns type="Number">k value.</returns>
        /// <remarks>Algebraic Coding Theory, Pages 24-30.<code>
        ///     if k is odd
        ///         a*a^-1 = 1 mod b    ---> a^-1 = b - vpp
        ///         b*b^-1 = 1 mod a    ---> b^-1 = vpp
        ///     if k is even
        ///         a*a^-1 = 1 mod b    ---> a^-1 = upp
        ///         b*b^-1 = 1 mod a    ---> b^-1 = a - upp
        /// </code></remarks>
        // Initialize rpp and rp from two inputs a and b s.t. rpp >= rp
        var rp;     // initialized from a or b
        if (isZero(a)) {    // gcd = (0,b) = b
            copyArray(b, 0, rpp, 0, b.length);
            rpp.length = b.length;
            return 0;
        } else if (isZero(b)) {     // gcd = (a,0) = a
            copyArray(a, 0, rpp, 0, a.length);
            rpp.length = a.length;
            return 0;
        } else if (compareDigits(a, b) < 0) {
            rp = a.slice(0);
            copyArray(b, 0, rpp, 0, b.length); rpp.length = b.length;
        } else {
            rp = b.slice(0);
            copyArray(a, 0, rpp, 0, a.length); rpp.length = a.length;
        }

        normalizeDigitArray(rpp);
        normalizeDigitArray(rp);
        var q = new Array(rpp.length);
        var r = new Array(rpp.length);

        var v = new Array(rpp.length);
        var vppPresent = vpp !== undefined;
        var vp;
        if (vppPresent) {
            vp = new Array(rpp.length);
            vp[0] = 1; vp.length = 1;
            vpp[0] = 0; vpp.length = 1;
        }

        var up;
        var u = new Array(rpp.length);
        var uppPresent = upp !== undefined;
        if (uppPresent) {
            up = new Array(rpp.length);
            up[0] = 0; up.length = 1;
            upp[0] = 1; upp.length = 1;
        }

        // k starts at -1 so that on return, it is >=0.
        // In the following discussion, assume a<b and this is computing a^-1 mod b where (a,b)=1, a<b.
        // Initialize rp=a, rpp=b.
        // The integer k keeps track of the sign of a^-1 (0 = positive) in b = q*a + r with 0 = q*a + r mod b
        // such that for q=a^-1 and r=1 (which is gcd=1 for inverse to exist), we have q*a = (-1)^k mod b.
        // Thus, for odd k, q*a = -1 mod b, and a^-1 = b-q as in the description.
        var k = -1;

        // At the end, gcd = rp = (a,b)
        var upp_out = upp;
        var vpp_out = vpp;
        var rpp_out = rpp;
        var save;

        // Recycle u and v as temp variables in division (divRem).
        while (!isZero(rp)) {
            // rpp = q*rp + r: compute q, r
            divRem(rpp, rp, q, r, u, v);

            if (uppPresent) {
                // u = q*up + upp
                // upp=up, up=u, u=upp
                multiply(q, up, u);
                add(u, upp, u);
                normalizeDigitArray(u);
                save = upp;
                upp = up;
                up = u;
                u = save;
            }

            if (vppPresent) {
                // v = q*vp + vpp
                // vpp=vp, vp=v, v=vpp
                multiply(q, vp, v);
                add(v, vpp, v);
                normalizeDigitArray(v);
                save = vpp;
                vpp = vp;
                vp = v;
                v = save;
            }

            // rpp=rp, rp=r, r=rpp
            save = rpp;
            rpp = rp;
            rp = r;
            r = save;

            k++;
        }

        // copy to output upp, vpp, rpp
        if (uppPresent) {
            copyArray(upp, 0, upp_out, 0, upp.length); upp_out.length = upp.length;
        }
        if (vppPresent) {
            copyArray(vpp, 0, vpp_out, 0, vpp.length); vpp_out.length = vpp.length;
        }
        copyArray(rpp, 0, rpp_out, 0, rpp.length); rpp_out.length = rpp.length;

        return k;
    }

    function gcd(a, b, output) {
        /// <summary>Compute greatest common divisor or a and b.</summary>
        /// <param name="a" type="Array">First integer input.</param>
        /// <param name="b" type="Array">Second integer input.</param>
        /// <param name="output" type="Array" optional="true">GCD output (optional).</param>
        /// <returns type="Array">GCD(a,b), the same object as the output parameter if given or a new object otherwise.</returns>
        var aa = a;
        var bb = b;
        if (compareDigits(a, b) > 0) {
            aa = b;
            bb = a;
        }

        eea(aa, bb, undefined, undefined, output);
        return normalizeDigitArray(output);
    }

    function modInv(a, n, aInv, pad) {
        /// <summary>Modular multiplicative inverse a^-1 mod n.</summary>
        /// <param name="a" type="Array">The number to invert. Condition: a &lt; n, or the result would be n^-1 mod a.</param>
        /// <param name="n" type="Array">The modulus.</param>
        /// <param name="aInv" type="Array" optional="true">a^-1 mod n (optional).</param>
        /// <param name="pad" type="Boolean" optional="true">True to pad the returned value to the length of the modulus (optional).</param>
        /// <returns type="Array">a^-1 mod n. Same as the aInv parameter if the parameter is specified.</returns>
        //var gcd = eea(a, n, inv);
        var upp = new Array(n.length);
        var vpp = new Array(n.length);
        var rpp = new Array(n.length);
        var k = eea(a, n, vpp, upp, rpp);

        aInv = aInv || [];
        if (compareDigits(rpp, One) !== 0) {
            aInv[0] = NaN;
            aInv.length = 1;
        } else {
            // gcd = 1, there is an inverse.
            // Compute inverse from Berlekamp's EEA outputs.
            if ((k & 1) === 1) {
                subtract(n, upp, aInv);
            } else {
                copyArray(upp, 0, aInv, 0, upp.length); aInv.length = upp.length;
            }
            if (pad) {
                normalizeDigitArray(aInv, n.length, true);
            } else {
                normalizeDigitArray(aInv);
            }
        }

        return aInv;
    }

    function modExp(base, exponent, modulus, result) {
        /// <summary>Modular exponentiation in an integer group.</summary>
        /// <param name="base" type="Array">The base of the exponentiation.</param>
        /// <param name="exponent" type="Array">The exponent.</param>
        /// <param name="modulus" type="Array">Modulus to reduce the result.</param>
        /// <param name="result" type="Array" optional="true">Output element that takes the modular exponentiation result (optional).</param>
        /// <returns type="Array">Modular exponentiation result, same as <paramref name="result"/> if not null, or a new object.</returns>

        result = result || [];

        // If exponent is 0 return 1
        if (compareDigits(exponent, Zero) === 0) {
            result[0] = 1;
        } else if (compareDigits(exponent, One) === 0) {
            // If exponent is 1 return valueElement
            copyArray(base, 0, result, 0, base.length);
            result.length = base.length;
        } else {
            var montmul = new MontgomeryMultiplier(modulus);
            normalizeDigitArray(base, montmul.s, true);
            montmul.modExp(
                base,
                exponent,
                result);
            result.length = modulus.length;
        }

        return result;
    }

    function MontgomeryMultiplier(modulus) {
        /// <summary>Construct a new montgomeryMultiplier object with the given modulus.</summary>
        /// <param name="modulus" type="Array">A prime modulus in little-endian digit form</param>
        /// <remarks>Montgomery Multiplier class
        /// This class implements high performance montgomery multiplication using
        /// CIOS, as well as modular exponentiation.</remarks>

        function computeM0Prime(m0) {
            /// <summary>Compute m' = -(m^-1) mod b, 16 bit digits. Based on Tolga Acar's code.</summary>
            /// <param name="m0" type="Number" integer="true">Digit m.</param>
            /// <returns type="Number">Digit m'.</returns>
            var m0Pr = 1;
            var a = 2;
            var b = 3;
            var c = b & m0;

            for (var i = 2; i <= DIGIT_BITS; i += 1) {
                if (a < c) {
                    m0Pr += a;
                }

                a = a << 1;
                b = (b << 1) | 1;
                c = m0 * m0Pr & b;
            }

            var result = (~m0Pr & DIGIT_MASK) + 1;
            return result;
        }

        function montgomeryMultiply(multiplicant, multiplier, result, ctx) {
            /// <summary>Montgomery multiplication with the CIOS method.</summary>
            /// <param name="multiplicant" type="Array">Multiplicant.</param>
            /// <param name="multiplier" type="Array">Multiplier.</param>
            /// <param name="result" type="Array">Computed result multiplicant * multiplier * r^-1 mod n.</param>
            /// <param name="ctx" type="MontgomeryMultiplier" optional="true">Context (optional = this).</param>

            ctx = ctx || this;

            // Upper digits of result
            var resultHigh = 0;

            // Precompute offsets
            var s = ctx.m.length;
            var sMinus1 = s - 1;

            // Local cache of m0, m', digitmask, digitbits
            var mPrime = ctx.mPrime;
            var m0 = ctx.m0;
            var left0 = multiplicant[0];

            var uv = 0, rightI, q, i, j, k;

            // Clear the result array
            for (i = 0; i < s; i += 1) {
                result[i] = 0;
            }

            for (i = 0; i < s; i += 1) {

                rightI = multiplier[i]; // Cache array value

                // 'u <- 0'

                // ---- UNROLL FIRST ITERATION (j == 0) ----
                uv = left0 * rightI + result[0];
                result[0] = uv & DIGIT_MASK;
                uv = Math.floor(uv / DIGIT_BASE);

                // ---- REMAINING ITERATIONS ----
                for (j = 1; j < s; j += 1) {

                    // '(u,v) <- a_j * b_i + z_j + u'
                    // uv = uv >>> DIGIT_BITS;
                    // Don't shift: JS supports shifts over 32-bit integers, only.
                    uv = multiplicant[j] * rightI + result[j] + uv;
                    result[j] = uv & DIGIT_MASK;
                    uv = Math.floor(uv / DIGIT_BASE);
                }
                // -------------------------------

                // '(u,v) <- z_s + u'.
                // 'z_s <- v'.
                // 'z_s+1 <- u'.
                resultHigh = resultHigh + uv;

                // 'q <- z_0 * m'  mod  digitBase
                q = (result[0] * mPrime) & DIGIT_MASK;

                // '(u,v) <- z_0 + m_0 * q'
                uv = Math.floor((result[0] + (m0 * q)) / DIGIT_BASE);

                // For j from 1 by 1 to s-1
                for (j = 1, k = 0; j < s; j += 1, k++) {
                    // '(u,v) <- m_j * q + z_j + u'
                    uv = ctx.m[j] * q + result[j] + uv;

                    // 'z_j-1 <- v'
                    result[k] = uv & DIGIT_MASK;
                    uv = Math.floor(uv / DIGIT_BASE);
                }

                // '(u,v) <- z_s + u'.
                // 'z_s-1 <- v'.
                // 'z_s <- z_s+1 + u'.
                resultHigh += uv;
                result[sMinus1] = resultHigh & DIGIT_MASK;
                resultHigh = Math.floor(resultHigh / DIGIT_BASE);
            }

            // Subtract modulus

            // Make sure temp1 isn't also our passed-in result array.
            // This can happen if temp1 was returned as a result to a previous call.
            var resultMinusM = ctx.temp1 === result ? ctx.temp2 : ctx.temp1,
                carry = 0;

            for (i = 0; i < s; i += 1) {
                carry = result[i] - ctx.m[i] + (carry >> DIGIT_BITS);
                resultMinusM[i] = carry & DIGIT_MASK;
            }
            carry = (resultHigh & DIGIT_MASK) + (carry >> DIGIT_BITS);
            carry = (resultHigh >>> DIGIT_BITS) + (carry >> DIGIT_BITS);

            // Use carry as a mask to copy result into the return array
            for (i = 0; i < s; i += 1) {
                result[i] = (carry & (resultMinusM[i] ^ result[i])) ^ resultMinusM[i];
            }

            return;
        }

        function convertToMontgomeryForm( digits) {
            /// <summary>Convert the digits in standard form to Montgomery residue representation.</summary>
            /// <param name="digits" type="Array">Input digits to convert, and also the output converted digits.</param>

            // Pad missing digits with zeroes
            if (digits.length < this.s) {
                digits.length = this.s;
                for (var i = 0; i < this.s; i++) {
                    digits[i] = isNaN(digits[i]) ? 0 : digits[i];
                }
            }

            var result = createArray(digits.length);

            this.montgomeryMultiply(digits, this.rSquaredModm, result);
            for (i = 0; i < this.s; i += 1) {
                digits[i] = result[i];
            }
        }

        function convertToStandardForm(digits) {
            /// <summary>Convert from Montgomery residue representation to the standard form.</summary>
            /// <param name="digits" type="Array">Input digits to convert, and also the output converted digits.</param>
            this.montgomeryMultiply(digits, this.one, this.temp1);
            for (var i = 0; i < this.s; i += 1) {
                digits[i] = this.temp1[i];
            }
        }

        function modExp(base, exponent, result) {
            /// <summary>Compute base to exponent mod m into result.</summary>
            /// <param name="base" type="Array">Base of length s in the context.</param>
            /// <param name="exponent" type="Array">Exponent.</param>
            /// <param name="result" type="Array">Output as base raised to exponent, and reduced to the modulus in the context.</param>
            /// <returns type="Array">result base^exponent mod m; the same result object.</returns>

            // Skip leading zero bits in the exponent
            // The total number of bits to scan in the exponent must be an integral multiple of
            // the number of bits to use in the exponent.
            var i;
            var expBitsToScan = 2;  // scan 2 bits at a time
            var expMask = DIGIT_MASK >>> (DIGIT_BITS - expBitsToScan);
            for (i = exponent.length - 1; i > 0 && exponent[i] === 0; i--) {
            }
            var bitsToScan = i * DIGIT_BITS + bitScanForward(exponent[i]) + 1;
            bitsToScan = bitsToScan + (expBitsToScan - (bitsToScan % expBitsToScan));
            var shiftAmt = (bitsToScan % DIGIT_BITS) - expBitsToScan;
            if (shiftAmt < 0) {
                shiftAmt += DIGIT_BITS;
            }
            var mask = expMask << shiftAmt;

            // Prepare the precomputation table of base for k bits
            // base[0..3] = [r, r*base, r*base^2, r*base^3] mod m
            for (i = 1; i < baseTable.length; i++) {
                modMul(baseTable[i - 1], base, this.m, baseTable[i], temp1, temp2);
                normalizeDigitArray(baseTable[i], this.s, true);
            }

            // a is the running result: a = 1*r mod m
            // TODO: Skip the first loop iteration below to avoid 1*1 mod m (minor optimization)
            var fourthPower = new Array(this.s);
            var squared = result;
            var partialResult = temp2;
            copyArray(this.rModM, 0, partialResult, 0, this.s);

            // Scan the exponent expBitsToScan bits at a time
            var tableIndex;
            while (bitsToScan > 0) {
                // result <- Mont(a, a);
                // result <- Mont(result, a);
                this.montgomeryMultiply(partialResult, partialResult, squared);
                this.montgomeryMultiply(squared, squared, fourthPower);

                // tableIndex <- the current bits of the scanned exponent
                tableIndex = (exponent[Math.floor((bitsToScan - 1) / DIGIT_BITS)] & mask) >>> shiftAmt;

                // aDigits = result * table[tableIndex]
                this.montgomeryMultiply(fourthPower, baseTable[tableIndex], partialResult);

                bitsToScan = bitsToScan - expBitsToScan;
                shiftAmt = shiftAmt - expBitsToScan;
                mask = mask >>> expBitsToScan;
                if (mask === 0) {
                    mask = expMask << (DIGIT_BITS - expBitsToScan);
                    shiftAmt = DIGIT_BITS - expBitsToScan;
                }
            }

            // result = Mont(a, 1)
            this.montgomeryMultiply(partialResult, this.one, result);
            return result;
        }

        // Modulus
        var m = modulus;

        // First digit of modulus
        var m0 = m[0];

        // Operand size (number of digits)
        var s = m.length;

        // The number one - used by modpow
        var one = createArray(s);
        one[0] = 1;

        // Compute m' = -(m^-1) mod b used by CIOS
        var mPrime = computeM0Prime(m0);

        // Create r and compute r mod m
        // Since we are base b integers of length s, we want
        // 'r = b^n = b^s'.
        var quotient = createArray(2 * s + 1);
        var rRemainder = createArray(s + 1);        // becomes rModM
        var temp1 = createArray(2 * s + 1);
        var temp2 = createArray(2 * s + 1);
        var rDigits = rRemainder;
        rDigits[s] = 1;
        divRem(
            rDigits,
            m,
            quotient,
            rRemainder,
            temp1,
            temp2);
        var rModM = normalizeDigitArray(rRemainder, s, true);

        // Compute R^2 mod m
        var rSquaredModm = createArray(2 * s + 1);
        var rSquaredDigits = rSquaredModm;
        rSquaredDigits[s * 2] = 1;
        divRem(
            rSquaredDigits,
            m,
            quotient,
            rSquaredModm,
            temp1,
            temp2);
        normalizeDigitArray(rSquaredModm, s, true);

        // Ready to do MontMul now - compute R^3
        var rCubedModm = createArray(s);
        var ctx = {
            m: m, mPrime: mPrime, m0: m0, temp1: temp1, temp2: temp2
        };
        montgomeryMultiply(
            rSquaredModm,
            rSquaredModm,
            rCubedModm,
            ctx);

        // Allocate space for multi-bit modular exponentiation
        var baseTable = new Array(4);
        baseTable[0] = rModM;
        baseTable[1] = new Array(s);
        baseTable[2] = new Array(s);
        baseTable[3] = new Array(s);

        // Return a per-instance context for Montgomery multiplier.
        // There is no need to use the "new" keyword when using this function.
        return {
            // Modulus
            m: modulus,

            // First digit of modulus
            m0: m0,

            // Compute m' = -(m^-1) mod b used by CIOS
            mPrime: mPrime,

            rSquaredModm: rSquaredModm,
            s: s,
            rModM: rModM,
            rCubedModm: rCubedModm,
            one: one,
            temp1: temp1,
            temp2: temp2,

            // Functions
            convertToMontgomeryForm: convertToMontgomeryForm,
            convertToStandardForm: convertToStandardForm,
            montgomeryMultiply: montgomeryMultiply,
            modExp: modExp
        };
    }

    function IntegerGroup(modulusBytes) {
        /// <summary>Construct a new IntegerGroup object with the given modulus.</summary>
        /// <param name="modulusBytes" type="Array">A big-endian number to represent the modulus in a byte array.</param>
        /// <remarks>This class represents the set of integers mod n. It is meant to be used in
        /// a variety of situations, for example to perform operations in the additive
        /// or multiplicative groups mod n. The modulus can be an arbitrary integer and
        /// in the case that it is a prime p then the integer group is the field Fp. The
        /// user should be aware of what type of object the given modulus produces, and
        /// thus which operations are valid.</remarks>

        // Modulus
        var m_modulus = bytesToDigits(modulusBytes);

        // Length of an element in digits
        var m_digitWidth = m_modulus.length;

        // Setup numeric constants
        var m_zero = intToDigits(0, m_digitWidth);
        var m_one = intToDigits(1, m_digitWidth);

        // Temp storage.
        // Allocation in js is very slow, we use these temp arrays to avoid it.
        var temp0 = createArray(m_digitWidth);
        var temp1 = createArray(m_digitWidth);

        // Create montgomery multiplier object
        var montmul = new MontgomeryMultiplier(m_modulus);

        function createElementFromBytes(bytes) {
            /// <summary>Create a new element object from a byte value.</summary>
            /// <param name="bytes" type="Array">Desired element in big-endian format in an array of bytes.</param>
            /// <returns type="integerGroupElement">An element object representing the given element.</returns>
            var digits = bytesToDigits(bytes);

            // Check size of the new element
            if (cryptoMath.compareDigits(digits, this.m_modulus) >= 0) {
                // Too many digits
                throw new Error("The number provided is not an element of this group");
            }

            // expand to the group modulus length
            normalizeDigitArray(digits, this.m_digitWidth, true);
            return integerGroupElement(digits, this);
        }

        function createElementFromInteger(integer) {
            /// <summary>Create a new element object from an integer value.</summary>
            /// <param name="integer" type="Number" integer="true">Desired element as an integer.</param>
            /// <returns type="integerGroupElement">An element object representing the given element.</returns>
            var digits = intToDigits(integer, this.m_digitWidth);
            return integerGroupElement(digits, this);
        }

        function createElementFromDigits(digits) {
            /// <summary>Create a new element object from a digit array.</summary>
            /// <param name="digits" type="Array">Desired element as a digit array.</param>
            /// <returns type="integerGroupElement">Object initialized with the given value.</returns>
            cryptoMath.normalizeDigitArray(digits, this.m_digitWidth, true);
            return integerGroupElement(digits, this);
        }

        function equals(otherGroup) {
            /// <summary>Return true if the given object is equivalent to this one.</summary>
            /// <param name="otherGroup" type="IntegerGroup"/>)
            /// <returns type="Boolean">True if the given objects are equivalent.</returns>

            return compareDigits(this.m_modulus, otherGroup.m_modulus) === 0;
        }

        function add(addend1, addend2, sum) {
            /// <summary>Add this element to another element.</summary>
            /// <param name="addend1" type="integerGroupElement"/>
            /// <param name="addend2" type="integerGroupElement"/>
            /// <param name="sum" type="integerGroupElement"/>

            var i;
            var s = this.m_digitWidth;
            var result = sum.m_digits;
            cryptoMath.add(addend1.m_digits, addend2.m_digits, result);
            var mask = compareDigits(result, this.m_modulus) >= 0 ? DIGIT_MASK : 0;

            // Conditional reduction by the modulus (one subtraction, only) only if the sum>modulus in almost constant time.
            // The result is unmodified if the computed sum < modulus already.
            var carry = 0;
            for (i = 0; i < s; i += 1) {
                carry = result[i] - (this.m_modulus[i] & mask) + carry;
                result[i] = carry & DIGIT_MASK;
                carry = (carry >> DIGIT_BITS);
            }

            result.length = s;
        }

        function subtract(leftElement, rightElement, outputElement) {
            /// <summary>Subtract an element from another element.</summary>
            /// <param name="leftElement" type="integerGroupElement"/>
            /// <param name="rightElement" type="integerGroupElement"/>
            /// <param name="outputElement" type="integerGroupElement"/>

            var i, s = this.m_digitWidth;
            var result = outputElement.m_digits;
            var carry = cryptoMath.subtract(leftElement.m_digits, rightElement.m_digits, outputElement.m_digits);

            // Final borrow?
            if (carry === -1) {
                carry = 0;
                for (i = 0; i < s; i += 1) {
                    carry += result[i] + this.m_modulus[i];
                    result[i] = carry & DIGIT_MASK;
                    carry = carry >> DIGIT_BITS;
                }
            }
        }

        function inverse(element, outputElement) {
            /// <summary>Compute the modular inverse of the given element.</summary>
            /// <param name="element" type="integerGroupElement">The element to be inverted.</param>
            /// <param name="outputElement" type="integerGroupElement">Receives the inverse element.</param>
            cryptoMath.modInv(element.m_digits, this.m_modulus, outputElement.m_digits);
        }

        function multiply(multiplicant, multiplier, product) {
            /// <summary>Multiply an element by another element in the integer group.</summary>
            /// <param name="multiplicant" type="integerGroupElement">Multiplicand.</param>
            /// <param name="multiplier" type="integerGroupElement">Multiplier.</param>
            /// <param name="product" type="integerGroupElement">Product reduced by the group modulus.</param>
            /// <returns type="Array">Same as <paramref name="product"/>.</returns>

            return cryptoMath.modMul(multiplicant.m_digits, multiplier.m_digits, this.m_modulus, product.m_digits, temp0, temp1);
        }

        function modexp(valueElement, exponent, outputElement) {
            /// <summary>Modular exponentiation in an integer group.</summary>
            /// <param name="valueElement" type="integerGroupElement">The base input to the exponentiation.</param>
            /// <param name="exponent" type="Array">The exponentas an unsigned integer.</param>
            /// <param name="outputElement" type="integerGroupElement" optional="true">Output element that takes the modular exponentiation result.</param>
            /// <returns type="integerGroupElement">Computed result. Same as <paramref name="outputElement"/> if not null, a new object otherwise.</returns>

            outputElement = outputElement || integerGroupElement([], this);

            // If exponent is 0 return 1
            if (compareDigits(exponent, m_zero) === 0) {
                outputElement.m_digits = intToDigits(1, this.m_digitWidth);
            } else if (compareDigits(exponent, m_one) === 0) {
                // If exponent is 1 return valueElement
                for (var i = 0; i < valueElement.m_digits.length; i++) {
                    outputElement.m_digits[i] = valueElement.m_digits[i];
                }
                outputElement.m_digits.length = valueElement.m_digits.length;
            } else {
                this.montmul.modExp(
                valueElement.m_digits,
        exponent,
        outputElement.m_digits);
                outputElement.m_digits.length = this.montmul.s;
            }

            return outputElement;
        }

        function integerGroupElement(digits, group) {
            /// <summary>integerGroupElement inner class.
            /// Create a new integer element mod n.
            /// </summary>
            /// <param name="digits" type="Array">
            /// An array of digits representing the element.
            /// </param>
            /// <param name="group" type="IntegerGroup">
            /// The parent group to which this element belongs.
            /// </param>

            // The value given in digits
            // must be &gt;= 0 and &;lt; modulus. Note that the constructor should not be
            // visible to the user, user should use group.createElementFromDigits(). This way we
            // can use any digit size and endian-ness we wish internally, operating in
            // our chosen representation until such time as the user wishes to produce
            // a byte array as output, which will be done by calling
            // toByteArrayUnsigned(). Note that other properties and methods are meant
            // to be "public" of course and thus callable by the user.

            return {
                // Variables
                m_digits: digits,
                m_group: group,

                // Functions
                equals: function (element) {
                    /// <summary>Compare an elements to this for equality.</summary>
                    /// <param name="element" type="integerGroupElement">Element to compare.</param>
                    /// <returns>True if elements are equal, false otherwise.</returns>
                    return (compareDigits(this.m_digits, element.m_digits) === 0) &&
                    this.m_group.equals(this.m_group, element.m_group);
                }
            };
        }

        return {
            // Variables
            m_modulus: m_modulus,
            m_digitWidth: m_digitWidth,
            montmul: montmul,

            // Functions
            createElementFromInteger: createElementFromInteger,
            createElementFromBytes: createElementFromBytes,
            createElementFromDigits: createElementFromDigits,
            equals: equals,
            add: add,
            subtract: subtract,
            multiply: multiply,
            inverse: inverse,
            modexp: modexp
        };
    }

    return {
        DIGIT_BITS: DIGIT_BITS,
        DIGIT_NUM_BYTES: DIGIT_NUM_BYTES,
        DIGIT_MASK: DIGIT_MASK,
        DIGIT_BASE: DIGIT_BASE,
        DIGIT_MAX: DIGIT_MAX,
        Zero: Zero,
        One: One,

        normalizeDigitArray: normalizeDigitArray,
        swapEndianness: swapEndianness,
        bytesToDigits: bytesToDigits,
        stringToDigits: stringToDigits,
        digitsToString: digitsToString,
        intToDigits: intToDigits,
        digitsToBytes: digitsToBytes,
        sequenceEqual: sequenceEqual,
        isZero: isZero,
        isEven: isEven,

        powerOfTwo: powerOfTwo,
        shiftRight: shiftRight,
        shiftLeft: shiftLeft,
        compareDigits: compareDigits,
        computeBitArray: computeBitArray,
        bitLength: highestSetBit,

        fixedWindowRecode: fixedWindowRecode,
        IntegerGroup: IntegerGroup,

        add: add,
        subtract: subtract,
        multiply: multiply,
        divRem: divRem,
        reduce: reduce,
        modInv: modInv,
        modExp: modExp,
        modMul: modMul,
        MontgomeryMultiplier: MontgomeryMultiplier,
        gcd: gcd
    };
}

var cryptoMath = cryptoMath || msrcryptoMath();

/// cryptoECC.js ==================================================================================
/// Implementation of Elliptic Curve math routines for cryptographic applications.

function MsrcryptoECC() {
    /// <summary>Elliptic Curve Cryptography (ECC) functions.</summary>

    // Create an array, mimics the constructors for typed arrays.
    function createArray( parameter) {
        var i, array = null;
        if (!arguments.length || typeof arguments[0] === "number") {
            // A number.
            array = [];
            for (i = 0; i < parameter; i += 1) {
                array[i] = 0;
            }
        } else if (typeof arguments[0] === "object") {
            // An array or other index-able object
            array = [];
            for (i = 0; i < parameter.length; i += 1) {
                array[i] = parameter[i];
            }
        }
        return array;
    }

    var btd = cryptoMath.bytesToDigits;

    var EllipticCurveFp = function (p1, a1, b1, order, gx, gy) {
        /// <param name="p1" type="Digits"/>
        /// <param name="a1" type="Digits"/>
        /// <param name="b1" type="Digits"/>
        /// <param name="order" type="Digits"/>
        /// <param name="gx" type="Digits"/>
        /// <param name="gy" type="Digits"/>
        /// <returns type="EllipticCurveFp"/>

        var fieldStorageBitLength = p1.length;

        var generator = EllipticCurvePointFp(this, false, gx, gy, null, false);

        return {
            p: p1,                  // field prime
            a: a1,                  // Weierstrass coefficient a
            b: b1,                  // Weierstrass coefficient b
            order: order,           // EC group order
            generator: generator,   // EC group generator
            allocatePointStorage: function () {
                return EllipticCurvePointFp(
                    this,
                    false,
                    cryptoMath.intToDigits(0, fieldStorageBitLength),
                    cryptoMath.intToDigits(0, fieldStorageBitLength)
                    );
            },
            createPointAtInfinity: function () {
                return EllipticCurvePointFp(
                    this,
                    true,
                    cryptoMath.intToDigits(0, fieldStorageBitLength),
                    cryptoMath.intToDigits(0, fieldStorageBitLength)
                    );
            }
        };
    };

    var createWeierstrassCurve = function (curveData) {

        var newCurve = new EllipticCurveFp(
            btd(curveData.p), // P
            btd(curveData.a), // A
            btd(curveData.b), // B
            btd(curveData.order), // Order
            btd(curveData.gx), // gX
            btd(curveData.gy)  // gy
        );

        newCurve.type = curveData.type;
        newCurve.name = curveData.name;
        newCurve.generator.curve = newCurve;

        return newCurve;
    };

    var createTedCurve = function (curveData) {

        var btd = cryptoMath.bytesToDigits;

        var newCurve = new EllipticCurveFp(
            btd(curveData.p), // P
            btd(curveData.a), // A
            btd(curveData.d), // D
            btd(curveData.order), // Order
            btd(curveData.gx), // gX
            btd(curveData.gy)  // gy
        );

        newCurve.type = curveData.type;

        if (newCurve.type == 1) {
            newCurve.d = newCurve.b.slice();
            delete newCurve.b;
        }

        newCurve.rbits = curveData.info[2];
        newCurve.name = curveData.name;
        newCurve.generator.curve = newCurve;

        return newCurve;
    };

    var EllipticCurvePointFp = function (curve, isInfinity, x, y, z, isInMontgomeryForm) {
        /// <param name="curve" type="EllipticCurveFp"/>
        /// <param name="isInfinity" type="Boolean"/>
        /// <param name="x" type="Digits"/>
        /// <param name="y" type="Digits"/>
        /// <param name="z" type="Digits" optional="true"/>
        /// <param name="isInMontgomeryForm" type="Boolean" optional="true"/>
        /// <returns type="EllipticCurvePointFp"/>

        var returnObj;

        // 'optional' parameters
        if (typeof z === "undefined") {
            z = null;
        }

        if (typeof isInMontgomeryForm === "undefined") {
            isInMontgomeryForm = false;
        }

        function equals( ellipticCurvePointFp) {
            /// <param name="ellipticCurvePointFp" type="EllipticCurvePointFp"/>

            // If null
            if (!ellipticCurvePointFp) {
                return false;
            }

            // Infinity == infinity
            if (returnObj.isInfinity && ellipticCurvePointFp.isInfinity) {
                return true;
            }

            // Otherwise its member-wise comparison

            if (returnObj.z === null && ellipticCurvePointFp.z !== null) {
                return false;
            }

            if (returnObj.z !== null && ellipticCurvePointFp.z === null) {
                return false;
            }

            if (returnObj.z === null) {
                return (cryptoMath.compareDigits(returnObj.x, ellipticCurvePointFp.x) === 0 &&
                         cryptoMath.compareDigits(returnObj.y, ellipticCurvePointFp.y) === 0 &&
                         returnObj.isInMontgomeryForm === ellipticCurvePointFp.isInMontgomeryForm);
            }

            return (cryptoMath.compareDigits(returnObj.x, ellipticCurvePointFp.x) === 0 &&
                    cryptoMath.compareDigits(returnObj.y, ellipticCurvePointFp.y) === 0 &&
                    cryptoMath.compareDigits(returnObj.z, ellipticCurvePointFp.z) === 0 &&
                    returnObj.isInMontgomeryForm === ellipticCurvePointFp.isInMontgomeryForm);
        }

        function copyTo( source, destination) {
            /// <param name="source" type="EllipticCurvePointFp"/>
            /// <param name="destination" type="EllipticCurvePointFp"/>

            destination.curve = source.curve;
            destination.x = source.x.slice();
            destination.y = source.y.slice();

            if (source.z !== null) {
                destination.z = source.z.slice();
            } else {
                destination.z = null;
            }

            setterSupport || (destination.isAffine = source.isAffine);
            destination.isInMontgomeryForm = source.isInMontgomeryForm;
            destination.isInfinity = source.isInfinity;

            if (!destination.equals(source)) {
                throw new Error("Instances should be equal.");
            }

        }

        function clone() {

            var clonePoint = EllipticCurvePointFp(
                    returnObj.curve,
                    returnObj.isInfinity,
                    createArray(returnObj.x),
                    createArray(returnObj.y),
                    returnObj.z ? createArray(returnObj.z) : null,
                    returnObj.isInMontgomeryForm);

            returnObj.ta && (clonePoint.ta = createArray(returnObj.ta));
            returnObj.tb && (clonePoint.tb = createArray(returnObj.tb));

            return clonePoint;
        }

        returnObj = {
            equals: function (ellipticCurvePointFp) {
                return equals(ellipticCurvePointFp);
            },
            copy: function (destination) {
                copyTo(this, destination);
                return;
            },
            clone: function () {
                return clone();
            }
        };

        createProperty(returnObj, "curve", curve, function () { return curve; }, function (val) { curve = val; });

        createProperty(returnObj, "x", x, function () { return x; }, function (val) { x = val; });
        createProperty(returnObj, "y", y, function () { return y; }, function (val) { y = val; });
        createProperty(returnObj, "z", z, function () { return z; }, function (val) { z = val; });

        createProperty(returnObj, "isInMontgomeryForm", isInMontgomeryForm, function () { return isInMontgomeryForm; }, function (val) { isInMontgomeryForm = val; });
        createProperty(returnObj, "isInfinity", isInfinity, function () { return isInfinity; }, function (val) { isInfinity = val; });
        createProperty(returnObj, "isAffine", (z === null), function () { return (z === null); });

        return returnObj;
    };

    var EllipticCurveOperatorFp = function (curve) {
        /// <param name="curve" type="EllipticCurveFp"/>

        // Store a reference to the curve.
        var m_curve = curve;

        var tedCurve = (curve.type === 1);

        var fieldElementWidth = curve.p.length;

        var montgomeryMultiplier = cryptoMath.MontgomeryMultiplier(curve.p);

        // Pre-compute and store the montgomeryized form of A, and set our
        // zero flag to determine whether or not we should use implementations
        // optimized for A = 0.
        var montgomerizedA = curve.a.slice();
        montgomeryMultiplier.convertToMontgomeryForm(montgomerizedA);

        var aequalsZero = cryptoMath.isZero(curve.a);

        var one = cryptoMath.One;

        var onemontgomery = createArray(fieldElementWidth);
        onemontgomery[0] = 1;
        montgomeryMultiplier.convertToMontgomeryForm(onemontgomery);

        var group = cryptoMath.IntegerGroup(cryptoMath.digitsToBytes(montgomeryMultiplier.m), true);

        // Setup temp storage.
        var temp0 = createArray(fieldElementWidth);
        var temp1 = createArray(fieldElementWidth);
        var temp2 = createArray(fieldElementWidth);
        var temp3 = createArray(fieldElementWidth);
        var temp4 = createArray(fieldElementWidth);
        var temp5 = createArray(fieldElementWidth);
        var temp6 = createArray(fieldElementWidth);
        var temp7 = createArray(fieldElementWidth);
        var swap0 = createArray(fieldElementWidth);

        // Some additional temp storage used in point conversion routines.
        var conversionTemp0 = createArray(fieldElementWidth);
        var conversionTemp1 = createArray(fieldElementWidth);
        var conversionTemp2 = createArray(fieldElementWidth);

        function modSub(left, right, result) {
            var resultElement = group.createElementFromInteger(0);
            resultElement.m_digits = result;
            group.subtract(
                group.createElementFromDigits(left),
                group.createElementFromDigits(right),
                resultElement);
        }

        function modAdd(left, right, result) {
            var resultElement = group.createElementFromInteger(0);
            resultElement.m_digits = result;
            group.add(
                group.createElementFromDigits(left),
                group.createElementFromDigits(right),
                resultElement);
        }

        function modInv(number, result) {
            cryptoMath.modInv(number, m_curve.p, result);
        }

        function modDivByTwo( dividend, result) {

            var s = dividend.length;

            var modulus = curve.p;

            // If dividend is odd, add modulus
            if ((dividend[0] & 0x1) === 0x1) {
                var carry = 0;

                for (var i = 0; i < s; i += 1) {
                    carry += dividend[i] + modulus[i];
                    result[i] = carry & cryptoMath.DIGIT_MASK;
                    carry = (carry >>> cryptoMath.DIGIT_BITS);
                }

                // Put carry bit into position for masking in
                carry = carry << (cryptoMath.DIGIT_BITS - 1);

                // Bit shift
                cryptoMath.shiftRight(result, result);

                // Mask in the carry bit
                result[s - 1] |= carry;
            } else {
                // Shift directly into result
                cryptoMath.shiftRight(dividend, result);
            }

        }

        function montgomeryMultiply(left, right, result) {
            montgomeryMultiplier.montgomeryMultiply(
                left,
                right,
                result);
        }

        function montgomerySquare(left, result) {
            montgomeryMultiplier.montgomeryMultiply(
                left,
                left,
                result);
        }

        function correctInversion(digits) {
            /// <param name="digits" type="Digits"/>
            var results = createArray(digits.length);
            montgomeryMultiply(digits, montgomeryMultiplier.rCubedModm, results);
            for (var i = 0; i < results.length; i += 1) {
                digits[i] = results[i];
            }
        }

        function doubleAequalsNeg3(point, outputPoint) {
            /// <param name="point" type="EllipticCurvePointFp"/>
            /// <param name="outputPoint" type="EllipticCurvePointFp"/>

            // If point = infinity then outputPoint := infinity.
            if (point.isInfinity) {
                outputPoint.isInfinity = true;
                return;
            }

            // t1 = z^2
            montgomerySquare(point.z, temp1);

            // t4 = zy
            montgomeryMultiply(point.z, point.y, temp4);

            // t2 = x + z^2
            // t2 = x + t1
            modAdd(point.x, temp1, temp2);

            // t1 = x - z^2
            // t1 = x - t1
            modSub(point.x, temp1, temp1);

            // Zfinal = zy
            outputPoint.z = temp4.slice();

            // t3 = (x + z^2)(x - z^2)
            montgomeryMultiply(temp1, temp2, temp3);

            // t2 = (x + z^2)(x - z^2)/2
            modDivByTwo(temp3, temp2);

            // t1 = alpha = 3(x + z^2)(x - z^2)/2
            modAdd(temp3, temp2, temp1);

            // t2 = y^2
            montgomerySquare(point.y, temp2);

            // t4 = alpha^2
            montgomerySquare(temp1, temp4);

            // t3 = beta = xy^2
            montgomeryMultiply(point.x, temp2, temp3);

            // t4 = alpha^2-beta
            modSub(temp4, temp3, temp4);

            // Xfinal = alpha^2-2beta
            modSub(temp4, temp3, outputPoint.x);

            // t4 = beta-Xfinal
            modSub(temp3, outputPoint.x, temp4);

            // t3 = y^4
            montgomerySquare(temp2, temp3);

            // t3 = y^4
            montgomeryMultiply(temp1, temp4, temp2);

            // Yfinal = alpha.(beta-Xfinal)-y^4
            modSub(temp2, temp3, outputPoint.y);

            // Finalize the flags on the output point.
            outputPoint.isInfinity = false;
            outputPoint.isInMontgomeryForm = true;
        }

        function doubleAequals0(point, outputPoint) {
            /// <param name="point" type="EllipticCurvePointFp"/>
            /// <param name="outputPoint" type="EllipticCurvePointFp"/>

            // If point = infinity then outputPoint := infinity.
            if (point.isInfinity) {
                outputPoint.isInfinity = true;
                return;
            }

            // 't3:=Y1^2;'
            montgomerySquare(point.y, temp3);

            // 't4:=X1^2;'
            montgomerySquare(point.x, temp4);

            // 't4:=3*t4;'
            modAdd(temp4, temp4, temp0);
            modAdd(temp0, temp4, temp4);

            // 't5:=X1*t3;'
            montgomeryMultiply(point.x, temp3, temp5);

            // 't0:=t3^2;'
            montgomerySquare(temp3, temp0);

            // 't1:=t4/2;'
            modDivByTwo(temp4, temp1);

            // 't3:=t1^2;'
            montgomerySquare(temp1, temp3);

            // 'Z_out:=Y1*Z1;'
            montgomeryMultiply(point.y, point.z, swap0);
            for (var i = 0; i < swap0.length; i += 1) {
                outputPoint.z[i] = swap0[i];
            }

            // 'X_out:=t3-2*t5;'
            modSub(temp3, temp5, outputPoint.x);
            modSub(outputPoint.x, temp5, outputPoint.x);

            // 't4:=t5-X_out;'
            modSub(temp5, outputPoint.x, temp4);

            // 't2:=t1*t4;'
            montgomeryMultiply(temp1, temp4, temp2);

            // 'Y_out:=t2-t0;'
            modSub(temp2, temp0, outputPoint.y);

            // Finalize the flags on the output point.
            outputPoint.isInfinity = false;
            outputPoint.isInMontgomeryForm = true;
        }

        // Given a povar P on an elliptic curve, return a table of
        // size 2^(w-2) filled with pre-computed values for
        // P, 3P, 5P, ... Etc.
        function generatePrecomputationTable(w, generatorPoint) {
            /// <summary>Given a point P on an elliptic curve, return a table of
            /// size 2^(w-2) filled with pre-computed values for
            /// P, 3P, 5P, ... Etc.</summary>
            /// <param name="w" type="Array">Window size</param>
            /// <param name="generatorPoint" type="EllipticCurvePointFp"></param>
            /// <returns type="Array">Precomputation table</returns>

            var validationPoint = generatorPoint.clone();
            convertToStandardForm(validationPoint);
            if (!validatePoint(validationPoint)) {
                throw new Error("Invalid Parameter");
            }

            // Create a Jacobian clone
            var pointJac = generatorPoint.clone();
            convertToJacobianForm(pointJac);

            var tablePos = [generatorPoint.clone()];

            // Q := P;
            var qJac = pointJac.clone();

            // Px2 = 2 * P
            var px2 = pointJac.clone();
            double(pointJac, px2);
            convertToAffineForm(px2);

            var qAff;

            for (var i = 1; i < Math.pow(2, w - 2) ; i++) {

                //Q := Q+P2;
                mixedAdd(qJac, px2, qJac);

                qAff = qJac.clone();
                convertToAffineForm(qAff);

                tablePos[i] = qAff;
            }

            return tablePos;
        }

        function double(point, outputPoint) {
            /// <param name="point" type="EllipticCurvePointFp"/>
            /// <param name="outputPoint" type="EllipticCurvePointFp"/>

            if (typeof point === "undefined") {
                throw new Error("point undefined");
            }
            if (typeof outputPoint === "undefined") {
                throw new Error("outputPoint undefined");
            }

            //// if (!point.curve.equals(outputPoint.curve)) {
            ////    throw new Error("point and outputPoint must be from the same curve object.");
            //// }

            if (point.isAffine) {
                throw new Error("Given point was in Affine form. Use convertToJacobian() first.");
            }

            if (!point.isInMontgomeryForm) {
                throw new Error("Given point must be in Montgomery form. Use montgomeryize() first.");
            }

            // Currently we support only two curve types, those with A=-3, and
            // those with A=0. In the future we will implement general support.
            // For now we switch here, assuming that the curve was validated in
            // the constructor.
            if (aequalsZero) {
                doubleAequals0(point, outputPoint);
            } else {
                doubleAequalsNeg3(point, outputPoint);
            }

        }

        function mixedDoubleAdd(jacobianPoint, affinePoint, outputPoint) {
            /// <param name="jacobianPoint" type="EllipticCurvePointFp"/>
            /// <param name="affinePoint" type="EllipticCurvePointFp"/>
            /// <param name="outputPoint" type="EllipticCurvePointFp"/>

            if (jacobianPoint.isInfinity) {
                affinePoint.copy(outputPoint);
                this.convertToJacobianForm(outputPoint);
                return;
            }

            if (affinePoint.isInfinity) {
                jacobianPoint.copy(outputPoint);
                return;
            }

            // Ok then we do the full double and add.

            // Note: in pseudo-code the capital X,Y,Z is Jacobian point, lower
            // case x, y, z is Affine point.

            // 't5:=Z1^ 2;'
            montgomerySquare(jacobianPoint.z, temp5);

            // 't6:=Z1*t5;'
            montgomeryMultiply(jacobianPoint.z, temp5, temp6);

            // 't4:=x2*t5;'
            montgomeryMultiply(affinePoint.x, temp5, temp4);

            // 't5:=y2*t6;'
            montgomeryMultiply(affinePoint.y, temp6, temp5);

            // 't1:=t4-X1;'
            modSub(temp4, jacobianPoint.x, temp1);

            // 't2:=t5-Y1;'
            modSub(temp5, jacobianPoint.y, temp2);

            //if t1 eq 0 then
            //    if t2 eq 0 then
            //        X2,Y2,Z2 := DBL(X1,Y1,Z1,prime,rr,m,RR);
            //        return mADD(X2,Y2,Z2,x2,y2,prime,rr,m,RR);
            //    else
            //        return X1,Y1,Z1;
            //    end if;
            //end if;

            // 't4:=t2^2;'
            montgomerySquare(temp2, temp4);

            // 't6:=t1^2;'
            montgomerySquare(temp1, temp6);

            // 't5:=t6*X1;'
            montgomeryMultiply(temp6, jacobianPoint.x, temp5);

            // 't0:=t1*t6;'
            montgomeryMultiply(temp1, temp6, temp0);

            // 't3:=t4-2*t5;'
            modSub(temp4, temp5, temp3);
            modSub(temp3, temp5, temp3);

            // 't4:=Z1*t1;'
            montgomeryMultiply(jacobianPoint.z, temp1, temp4);

            // 't3:=t3-t5;'
            modSub(temp3, temp5, temp3);

            // 't6:=t0*Y1;'
            montgomeryMultiply(temp0, jacobianPoint.y, temp6);

            // 't3:=t3-t0;'
            modSub(temp3, temp0, temp3);

            var temp3isZero = true;

            for (var i = 0; i < temp3.length; i++) {
                if (temp3[i] !== 0) {
                    temp3isZero = false;
                    break;
                }
            }

            if (temp3isZero) {
                for (i = 0; i < outputPoint.x.length; i++) {
                    outputPoint.x[i] = 0;
                    outputPoint.y[i] = 0;
                    outputPoint.z[i] = 0;
                }
                outputPoint.y[0] = 1;
                return;
            }

            //if t3 eq 0 then
            //    return 0,1,0;
            //end if;

            // 't1:=2*t6;'
            modAdd(temp6, temp6, temp1);

            // 'Zout:=t4*t3;'
            montgomeryMultiply(temp4, temp3, outputPoint.z);

            // 't4:=t2*t3;'
            montgomeryMultiply(temp2, temp3, temp4);

            // 't0:=t3^2;'
            montgomerySquare(temp3, temp0);

            // 't1:=t1+t4;'
            modAdd(temp1, temp4, temp1);

            // 't4:=t0*t5;'
            montgomeryMultiply(temp0, temp5, temp4);

            // 't7:=t1^2;'
            montgomerySquare(temp1, temp7);

            // 't4:=t0*t5;'
            montgomeryMultiply(temp0, temp3, temp5);

            // 'Xout:=t7-2*t4;'
            modSub(temp7, temp4, outputPoint.x);
            modSub(outputPoint.x, temp4, outputPoint.x);

            // 'Xout:=Xout-t5;'
            modSub(outputPoint.x, temp5, outputPoint.x);

            // 't3:=Xout-t4;'
            modSub(outputPoint.x, temp4, temp3);

            // 't0:=t5*t6;'
            montgomeryMultiply(temp5, temp6, temp0);

            // 't4:=t1*t3;'
            montgomeryMultiply(temp1, temp3, temp4);

            // 'Yout:=t4-t0;'
            modSub(temp4, temp0, outputPoint.y);

            outputPoint.isInfinity = false;
            outputPoint.isInMontgomeryForm = true;

        }

        function mixedAdd(jacobianPoint, affinePoint, outputPoint) {
            /// <param name="jacobianPoint" type="EllipticCurvePointFp"/>
            /// <param name="affinePoint" type="EllipticCurvePointFp"/>
            /// <param name="outputPoint" type="EllipticCurvePointFp"/>

            if (jacobianPoint === null) {
                throw new Error("jacobianPoint");
            }

            if (affinePoint === null) {
                throw new Error("affinePoint");
            }

            if (outputPoint === null) {
                throw new Error("outputPoint");
            }

            if (jacobianPoint.curve !== affinePoint.curve ||
                jacobianPoint.curve !== outputPoint.curve) {
                throw new Error("All points must be from the same curve object.");
            }

            if (jacobianPoint.isAffine) {
                throw new Error(
                    "Given jacobianPoint was in Affine form. Use ConvertToJacobian() before calling DoubleJacobianAddAffinePoints().");
            }

            if (!affinePoint.isAffine) {
                throw new Error(
                    "Given affinePoint was in Jacobian form. Use ConvertToAffine() before calling DoubleJacobianAddAffinePoints().");
            }

            if (outputPoint.isAffine) {
                throw new Error(
                    "Given jacobianPoint was in Jacobian form. Use ConvertToJacobian() before calling DoubleJacobianAddAffinePoints().");
            }

            if (!jacobianPoint.isInMontgomeryForm) {
                throw new Error("Jacobian point must be in Montgomery form");
            }

            if (!affinePoint.isInMontgomeryForm) {
                throw new Error("Affine point must be in Montgomery form");
            }

            if (jacobianPoint.isInfinity) {
                affinePoint.copy(outputPoint);
                this.convertToJacobianForm(outputPoint);
                return;
            }

            if (affinePoint.isInfinity) {
                jacobianPoint.copy(outputPoint);
                return;
            }

            // Ok then we do the full double and add.

            // Note: in pseudo-code the capital X1,Y1,Z1 is Jacobian point,
            // lower case x2, y2, z2 is Affine point.

            //if (X1 eq 0) and (Y1 eq 1) and (Z1 eq 0) then
            //    z2 := ToMontgomery(1,prime,rr,m,RR);
            //    return x2,y2;
            //end if;
            //if (x2 eq 0) and (y2 eq 1) then
            //    return X1,Y1,Z1;
            //end if;

            // 't1 := Z1^2;'.
            montgomerySquare(jacobianPoint.z, temp1);

            // 't2 := t1 * Z1;'
            montgomeryMultiply(temp1, jacobianPoint.z, temp2);

            // 't3 := t1 * x2;'
            montgomeryMultiply(temp1, affinePoint.x, temp3);

            // 't4 := t2 * y2;'
            montgomeryMultiply(temp2, affinePoint.y, temp4);

            // 't1 := t3 - X1;'
            modSub(temp3, jacobianPoint.x, temp1);

            // 't2 := t4 - Y1;'
            modSub(temp4, jacobianPoint.y, temp2);

            // If t1 != 0 then
            var i;
            for (i = 0; i < temp1.length; i += 1) {
                if (temp1[i] !== 0) {

                    // 'Zout := Z1 * t1;'
                    montgomeryMultiply(jacobianPoint.z, temp1, temp0);
                    for (var j = 0; j < fieldElementWidth; j += 1) {
                        outputPoint.z[j] = temp0[j];
                    }

                    // 't3 := t1^2;'
                    montgomerySquare(temp1, temp3);

                    // 't4 := t3 * t1;'
                    montgomeryMultiply(temp3, temp1, temp4);

                    // 't5 := t3 * X1;'
                    montgomeryMultiply(temp3, jacobianPoint.x, temp5);

                    // 't1 := 2 * t5;'
                    modAdd(temp5, temp5, temp1);

                    // 'Xout := t2^2;'
                    montgomerySquare(temp2, outputPoint.x);

                    // 'Xout := Xout - t1;'
                    modSub(outputPoint.x, temp1, outputPoint.x);

                    // 'Xout := Xout - t4;'
                    modSub(outputPoint.x, temp4, outputPoint.x);

                    // 't3 := t5 - Xout;'
                    modSub(temp5, outputPoint.x, temp3);

                    // 't5 := t3*t2;'
                    montgomeryMultiply(temp2, temp3, temp5);

                    // 't6 := t4*Y1;'
                    montgomeryMultiply(jacobianPoint.y, temp4, temp6);

                    // 'Yout := t5-t6;'
                    modSub(temp5, temp6, outputPoint.y);

                    outputPoint.isInfinity = false;
                    outputPoint.isInMontgomeryForm = true;

                    return;
                }
            }

            // Else if T2 != 0 then
            for (i = 0; i < temp2.length; i += 1) {
                if (temp2[i] !== 0) {
                    //         Return infinity
                    outputPoint.isInfinity = true;
                    outputPoint.isInMontgomeryForm = true;
                    return;
                }
            }
            // Else use DBL routine to return 2(x2, y2, 1)
            affinePoint.copy(outputPoint);
            this.convertToJacobianForm(outputPoint);
            this.double(outputPoint, outputPoint);
            outputPoint.isInMontgomeryForm = true;

        }

        function scalarMultiply(k, point, outputPoint, muliplyBy4) {
            /// <param name="k" type="Digits"/>
            /// <param name="point" type="EllipticCurvePointFp"/>
            /// <param name="outputPoint" type="EllipticCurvePointFp"/>

            // Special case for the point at infinity or k == 0
            if (point.isInfinity || cryptoMath.isZero(k)) {
                outputPoint.isInfinity = true;
                return;
            }

            // Runtime check for 1 <= k < order to ensure we don't get hit by
            // subgroup attacks. Since k is a FixedWidth it is a positive integer
            // and we already checked for zero above. So it must be >= 1 already.
            if (cryptoMath.compareDigits(k, curve.order) >= 0) {
                throw new Error("The scalar k must be in the range 1 <= k < order.");
            }

            // copy k so we can modify it without modifying the passed in array.
            k = k.slice();

            if (point.curve.type === 1) {

                var pointIsEP = (typeof point.ta !== 'undefined');

                if (!pointIsEP) {
                    convertToExtendedProjective(point);
                }

                scalarMultiplyTed(k, point, outputPoint, muliplyBy4);

                // Convert the points back to standard if they arrived that way.
                if (!pointIsEP) {
                    normalizeTed(point);
                }

            } else {

                var pointIsMF = point.isInMontgomeryForm,
                    outputIsMF = outputPoint.isInMontgomeryForm,
                    outputIsAffine = outputPoint.isAffine;

                // Convert parameters to Montgomory form if not already.
                if (!pointIsMF) {
                    convertToMontgomeryForm(point);
                }

                if (!outputIsMF) {
                    convertToMontgomeryForm(outputPoint);
                }

                scalarMultiplyW(k, point, outputPoint);

                // outputPoint returns as Jacobian - convert back to original state.
                if (outputIsAffine) {
                    convertToAffineForm(outputPoint);
                }

                // Convert the points back to standard if they arrived that way.
                if (!pointIsMF) {
                    convertToStandardForm(point);
                }

                if (!outputIsMF) {
                    convertToStandardForm(outputPoint);
                }
            }

            return;

        }

        function scalarMultiplyW(k, point, outputPoint) {
            /// <param name="k" type="Digits"/>
            /// <param name="point" type="EllipticCurvePointFp"/>
            /// <param name="outputPoint" type="EllipticCurvePointFp"/>

            // The point should be in Montgomery form.
            var validationPoint = point.clone();
            convertToStandardForm(validationPoint);

            if (!validatePoint(validationPoint)) {
                throw new Error("Invalid Parameters.");
            }

            var odd = (k[0] & 1),
                tempk = [];

            // If (odd) then k = temp else k = k
            modSub(point.curve.order, k, tempk);
            for (i = 0; i < k.length; i++) {
                k[i] = ((odd - 1) & (k[i] ^ tempk[i])) ^ k[i];
            }

            // Change w based on the size of the digits,
            // 5 is good for 256 bits, use 6 for bigger sizes.
            var w = (fieldElementWidth <= 8) ? 5 : 6;
            var m = point.curve.p.length * cryptoMath.DIGIT_BITS;
            var t = Math.ceil(m / (w - 1));

            var kDigits = cryptoMath.fixedWindowRecode(k, w, t);

            var Tm = generatePrecomputationTable(w, point);

            var position =
                Math.floor(Math.abs(kDigits[t]) - 1) / 2;

            var Q = Tm[position].clone();
            convertToJacobianForm(Q);

            for (var i = t - 1; i >= 0; i--) {

                for (var j = 0; j < (w - 2) ; j++) {
                    double(Q, Q);
                }

                position = Math.floor((Math.abs(kDigits[i]) - 1) / 2);

                var L = tableLookupW(Tm, position);

                if (kDigits[i] < 0) {
                    negate(L, L);
                }

                mixedDoubleAdd(Q, L, Q);

            }

            // if k is even, negate Q
            modSub(point.curve.p, Q.y, tempk);
            for (i = 0; i < Q.y.length; i++) {
                Q.y[i] = ((odd - 1) & (Q.y[i] ^ tempk[i])) ^ Q.y[i];
            }

            Q.copy(outputPoint);

            return;

        }

        function tableLookupW(table, index) {

            var pos = (index + 1) % table.length;

            for (var i = 0; i < table.length; i++) {
                var L = table[pos].clone();
                pos = (pos + 1) % table.length;
            }

            return L;
        }

        function negate(point, outputPoint) {
            /// <param name="point" type="EllipticCurvePointFp">Input point to negate.</param>
            /// <param name="outputPoint" type="EllipticCurvePointFp">(x, p - y).</param>

            if (point !== outputPoint) {
                point.copy(outputPoint);
            }
            modSub(point.curve.p, point.y, outputPoint.y);
        }

        function convertToMontgomeryForm(point) {
            /// <param name="point" type="EllipticCurvePointFp"/>

            if (point.isInMontgomeryForm) {
                throw new Error("The given point is already in Montgomery form.");
            }

            if (!point.isInfinity) {
                montgomeryMultiplier.convertToMontgomeryForm(point.x);
                montgomeryMultiplier.convertToMontgomeryForm(point.y);

                if (point.z !== null) {
                    montgomeryMultiplier.convertToMontgomeryForm(point.z);
                }

                if (typeof point.ta !== 'undefined') {
                    montgomeryMultiplier.convertToMontgomeryForm(point.ta);
                    montgomeryMultiplier.convertToMontgomeryForm(point.tb);
                }
            }

            point.isInMontgomeryForm = true;
        }

        function convertToStandardForm(point) {
            /// <param name="point" type="EllipticCurvePointFp"/>

            if (!point.isInMontgomeryForm) {
                throw new Error("The given point is not in montgomery form.");
            }

            if (!point.isInfinity) {
                montgomeryMultiplier.convertToStandardForm(point.x);
                montgomeryMultiplier.convertToStandardForm(point.y);
                if (point.z !== null) {
                    montgomeryMultiplier.convertToStandardForm(point.z);
                }
                if (typeof point.ta !== 'undefined') {
                    montgomeryMultiplier.convertToStandardForm(point.ta);
                    montgomeryMultiplier.convertToStandardForm(point.tb);
                }
            }

            point.isInMontgomeryForm = false;

        }

        function convertToAffineForm(point) {
            /// <param name="point" type="EllipticCurvePointFp"/>

            if (point.isInfinity) {
                point.z = null;
                setterSupport || (point.isAffine = true);
                return;
            }

            // DETERMINE 1/Z IN MONTGOMERY FORM --------------------------------

            // Call out to the basic inversion function, not the one in this class.
            cryptoMath.modInv(point.z, curve.p, conversionTemp2, true);

            if (point.isInMontgomeryForm) {
                montgomeryMultiply(conversionTemp2, montgomeryMultiplier.rCubedModm, conversionTemp1);
                var swap = conversionTemp2;
                conversionTemp2 = conversionTemp1;
                conversionTemp1 = swap;
            }

            // CONVERT TO AFFINE COORDS ----------------------------------------

            // 'temp0 <- 1/z^2'
            montgomerySquare(conversionTemp2, conversionTemp0);

            // Compute point.x = x / z^2 mod p
            // NOTE: We cannot output directly to the X digit array since it is
            // used for input to the multiplication routine, so we output to temp1
            // and copy.
            montgomeryMultiply(point.x, conversionTemp0, conversionTemp1);
            for (var i = 0; i < fieldElementWidth; i += 1) {
                point.x[i] = conversionTemp1[i];
            }

            // Compute point.y = y / z^3 mod p
            // temp1 <- y * 1/z^2.
            montgomeryMultiply(point.y, conversionTemp0, conversionTemp1);
            // 'y <- temp1 * temp2 (which == 1/z)'
            montgomeryMultiply(conversionTemp1, conversionTemp2, point.y);

            // Finally, point.z = z / z mod p = 1
            // We use z = NULL for this case to make detecting Jacobian form
            // faster (otherwise we would have to scan the entire Z digit array).
            point.z = null;

            delete point.ta;
            delete point.tb;

            setterSupport || (point.isAffine = true);
        }

        function convertToJacobianForm(point) {
            /// <param name="point" type="EllipticCurvePointFp"/>

            if (!point.isAffine) {
                throw new Error("The given point is not in Affine form.");
            }

            setterSupport || (point.isAffine = false);

            var clonedDigits,
                i,
                zOne = point.isInMontgomeryForm ? onemontgomery : one;

            clonedDigits = createArray(zOne.length);
            for (i = 0; i < zOne.length; i += 1) {
                clonedDigits[i] = zOne[i];
            }

            point.z = clonedDigits;

            return;
        }

        function validatePoint(point) {
            /// <summary>
            /// Point validation
            //  Check if point P=(x,y) lies on the curve and if x,y are in [0, p-1]
            /// </summary>

            if (point.isInfinity) {
                return false;
            }

            // Does P lie on the curve?
            cryptoMath.modMul(point.y, point.y, point.curve.p, temp1)

            cryptoMath.modMul(point.x, point.x, point.curve.p, temp2);
            cryptoMath.modMul(point.x, temp2, point.curve.p, temp3);
            modAdd(temp3, point.curve.b, temp2);
            cryptoMath.modMul(point.x, point.curve.a, point.curve.p, temp3);
            modAdd(temp2, temp3, temp2);
            modSub(temp1, temp2, temp1);

            if (cryptoMath.isZero(temp1) == false) {
                return false;
            }

            return true;
        }

        /// Ted functions

        function validatePointTed(point) {

            if (point.ta) {
                point = point.clone();
                normalizeTed(point);
            }

            // Does P lie on the curve?
            cryptoMath.modMul(point.y, point.y, point.curve.p, temp3);
            cryptoMath.modMul(point.x, point.x, point.curve.p, temp2);

            cryptoMath.add(temp2, temp3, temp1);
            cryptoMath.reduce(temp4, point.curve.p, temp4);

            cryptoMath.modMul(temp2, temp3, point.curve.p, temp4);
            cryptoMath.modMul(point.curve.d, temp4, point.curve.p, temp3);

            cryptoMath.add(temp3, [1], temp2);
            cryptoMath.reduce(temp2, point.curve.p, temp2);

            cryptoMath.subtract(temp1, temp2, temp1);
            cryptoMath.reduce(temp1, point.curve.p, temp1);

            if (cryptoMath.isZero(temp1) == false) {
                return false;
            }

            return true;
        }

        function generatePrecomputationTableTed(npoints, point) {

            // Precomputation function, points are stored using representation (X,Y,Z,dT)
            // Twisted Edwards a=1 curve

            var Q = point.clone(),
                P2 = Q.clone(),
                T = [];

            // Generating P2 = 2(X1,Y1,Z1,T1a,T1b) -> (XP2,YP2,ZP2,d*TP2) and T[0] = P = (X1,Y1,Z1,T1a,T1b)
            T[0] = convert_R1_to_R2(point);
            doubleTed(Q, Q);
            P2 = convert_R1_to_R2(Q);
            Q = point.clone();

            for (var i = 1; i < npoints; i++) {
                // T[i] = 2P+T[i-1] = (2*i+1)P = (XP2,Y2P,ZP2,d*TP2) + (X_(2*i-1), Y_(2*i-1), Z_(2*i-1), Ta_(2*i-1), Tb_(2*i-1)) = (X_(2*i+1), Y_(2*i+1), Z_(2*i+1), d*T_(2*i+1))
                addTedExtended(P2, Q, Q);
                T[i] = convert_R1_to_R2(Q);
            }

            return T;
        }

        function convertToExtendedProjective(affinePoint) {
            affinePoint.ta = affinePoint.x.slice();
            affinePoint.tb = affinePoint.y.slice();
            affinePoint.z = [1];
        }

        function scalarMultiplyTed(k, point, outputPoint, multiplyBy4) {

            if (!validatePointTed(point)) {
                throw new Error("Invalid Parameter");
            }

            var rbits = point.curve.rbits;
            multiplyBy4 = typeof multiplyBy4 === 'undefined' ? true : multiplyBy4;

            var w = (fieldElementWidth <= 8) ? 5 : 6;

            var t = Math.floor((rbits + (w - 2)) / (w - 1));
            var i, j;

            // copy k so we can modify it without modifying the passed in array.
            k = k.slice();

            var T = point.clone();

            convertToExtendedProjective(T);

            if (multiplyBy4) {
                doubleTed(T, T);
                doubleTed(T, T);
            }

            var precomputationTable = generatePrecomputationTableTed(1 << (w - 2), T);

            var odd = (k[0] & 1),
                tempk = [];

            // If (odd) then k = temp else k = k
            modSub(point.curve.order, k, tempk);
            for (i = 0; i < k.length; i++) {
                k[i] = ((odd - 1) & (k[i] ^ tempk[i])) ^ k[i];
            }

            var kDigits = cryptoMath.fixedWindowRecode(k, w, t);

            var position =
                Math.floor(Math.abs(kDigits[t]) - 1) / 2;

            var R = precomputationTable[position];

            T.x = R.x.slice();
            T.y = R.y.slice();
            T.z = R.z.slice();

            for (i = t - 1; i >= 0; i--) {

                for (j = 0; j < (w - 1) ; j++) {
                    doubleTed(T, T);
                }

                position = Math.floor((Math.abs(kDigits[i]) - 1) / 2);

                var L = tableLookupTed(precomputationTable, position);

                if (kDigits[i] < 0) {
                    modSub(point.curve.p, L.x, L.x);
                    modSub(point.curve.p, L.td, L.td);
                }

                addTedExtended(L, T, T);
            }

            // If (odd) then T.x = temp else T.x = T.x
            modSub(point.curve.p, T.x, tempk);
            for (i = 0; i < T.x.length; i++) {
                T.x[i] = ((odd - 1) & (T.x[i] ^ tempk[i])) ^ T.x[i];
            }

            normalizeTed(T);

            outputPoint.x = T.x.slice();
            outputPoint.y = T.y.slice();

            return;

        }

        function tableLookupTed(table, index) {

            var pos = (index + 1) % table.length;

            for (var i = 0; i < table.length; i++) {
                var L = {
                    x: table[pos].x.slice(),
                    y: table[pos].y.slice(),
                    z: table[pos].z.slice(),
                    td: table[pos].td.slice()
                }
                pos = (pos + 1) % table.length;
            }

            return L;
        }

        function normalizeTed(point) {

            cryptoMath.modInv(point.z, curve.p, conversionTemp2, true);

            cryptoMath.modMul(point.x, conversionTemp2, curve.p, point.x);

            cryptoMath.modMul(point.y, conversionTemp2, curve.p, point.y);

            delete point.ta;
            delete point.tb;

            point.z = null;

            return;
        }

        function doubleTed(point, outputPoint) {

            if (typeof point.ta === 'undefined') {
                throw new Error("Point should be in Extended Projective form.");
            }

            // t0 = x1^2
            cryptoMath.modMul(point.x, point.x, point.curve.p, temp0);

            // t1 = y1^2
            cryptoMath.modMul(point.y, point.y, point.curve.p, temp1);

            // Ta = z1^2
            cryptoMath.modMul(point.z, point.z, point.curve.p, point.ta);

            // (new) Tbfinal = Y1^2-X1^2
            modSub(temp1, temp0, outputPoint.tb);

            //(new) t0 = X1^2+Y1^2
            modAdd(temp0, temp1, temp0);

            //(ok) Ta = 2z1^2
            modAdd(point.ta, point.ta, point.ta);

            // (ok) y = 2y1
            modAdd(point.y, point.y, point.y);

            // (new) t1 = 2z1^2-(X1^2+Y1^2)
            modSub(point.ta, temp0, temp1);

            // Tafinal = 2x1y1
            cryptoMath.modMul(point.x, point.y, point.curve.p, outputPoint.ta);

            // Yfinal = (x1^2+y1^2)(y1^2-x1^2)
            cryptoMath.modMul(temp0, outputPoint.tb, point.curve.p, outputPoint.y);

            // Xfinal = 2x1y1[2z1^2-(y1^2-x1^2)]
            cryptoMath.modMul(temp1, outputPoint.ta, point.curve.p, outputPoint.x);

            // Zfinal = (y1^2-x1^2)[2z1^2-(y1^2-x1^2)]
            cryptoMath.modMul(temp0, temp1, point.curve.p, outputPoint.z);

            return;
        }

        function addTed(point1 /*Q*/, point2 /*P*/, outputPoint) {

            var cm = cryptoMath;

            var modulus = point1.curve.p;
            var temp1 = [];

            if (typeof point1.ta === 'undefined') {
                throw new Error("Point1 should be in Extended Projective form.");
            }

            if (typeof point2.ta === 'undefined') {
                throw new Error("Point2 should be in Extended Projective form.");
            }

            var qq = convert_R1_to_R2(point1);

            addTedExtended(qq, point2, outputPoint);

            return;
        }

        function convert_R1_to_R2(point) {

            var curve = point.curve,
                modulus = curve.p,
                qq = {
                    x: point.x.slice(),
                    y: point.y.slice(),
                    z: point.z.slice(),
                    td: [],
                    curve: point.curve
                };

            cryptoMath.modMul(point.ta, point.tb, modulus, conversionTemp0);

            cryptoMath.modMul(conversionTemp0, curve.d, modulus, qq.td);

            return qq;
        }

        function addTedExtended(qq /*Q*/, point2 /*P*/, outputPoint) {

            // Complete point addition P = P+Q, including the cases P!=Q, P=Q, P=-Q, P=neutral and Q=neutral
            // Twisted Edwards a=1 curve
            // Inputs: P = (X1,Y1,Z1,Ta,Tb), where T1 = Ta*Tb, corresponding to extended twisted Edwards coordinates (X1:Y1:Z1:T1)
            //         Q = (X2,Y2,Z2,dT2), corresponding to extended twisted Edwards coordinates (X2:Y2:Z2:T2)
            // Output: P = (X1,Y1,Z1,Ta,Tb), where T1 = Ta*Tb, corresponding to extended twisted Edwards coordinates (X1:Y1:Z1:T1)

            var cm = cryptoMath;
            var modulus = point2.curve.p;

            temp1 = []; temp2 = []; temp3 = [];

            //FP_MUL(P->Z, Q->Z, t3);             // t3 = Z1*Z2
            cm.modMul(point2.z, qq.z, modulus, temp3);

            //FP_MUL(P->Ta, P->Tb, t1);           // t1 = T1
            cm.modMul(point2.ta, point2.tb, modulus, temp1);

            //FP_ADD(P->X, P->Y, P->Ta);          // Ta = (X1+Y1)
            modAdd(point2.x, point2.y, point2.ta);

            //FP_MUL(t1, Q->Td, t2);              // t2 = dT1*T2
            cm.modMul(temp1, qq.td, modulus, temp2);

            //FP_ADD(Q->X, Q->Y, P->Tb);          // Tb = (X2+Y2)
            modAdd(qq.x, qq.y, point2.tb);

            //FP_SUB(t3, t2, t1);                 // t1 = theta
            modSub(temp3, temp2, temp1);

            //FP_ADD(t3, t2, t3);                 // t3 = alpha
            modAdd(temp3, temp2, temp3);

            //FP_MUL(P->Ta, P->Tb, t2);           // t2 = (X1+Y1)(X2+Y2)
            cm.modMul(point2.ta, point2.tb, modulus, temp2);

            //FP_MUL(P->X, Q->X, P->Z);           // Z = X1*X2
            cm.modMul(point2.x, qq.x, modulus, point2.z);

            //FP_MUL(P->Y, Q->Y, P->X);           // X = Y1*Y2
            cm.modMul(point2.y, qq.y, modulus, point2.x);

            //FP_SUB(t2, P->Z, t2);
            modSub(temp2, point2.z, temp2);

            //FP_SUB(P->X, P->Z, P->Ta);          // Tafinal = omega = Y1*Y2-X1*X2
            modSub(point2.x, point2.z, outputPoint.ta);

            //FP_SUB(t2, P->X, P->Tb);            // Tbfinal = beta = (X1+Y1)(X2+Y2)-X1*X2-Y1*Y2
            modSub(temp2, point2.x, outputPoint.tb);

            //FP_MUL(P->Ta, t3, P->Y);            // Yfinal = alpha*omega
            cm.modMul(outputPoint.ta, temp3, modulus, outputPoint.y);

            //FP_MUL(P->Tb, t1, P->X);            // Xfinal = beta*theta
            cm.modMul(outputPoint.tb, temp1, modulus, outputPoint.x);

            //FP_MUL(t3, t1, P->Z);               // Zfinal = theta*alpha
            cm.modMul(temp3, temp1, modulus, outputPoint.z);

            return;
        }

        function convertTedToWeierstrass(tedPoint, wPoint) {
            /// <summary></summary>
            /// <param name="tedPoint" type=""></param>
            /// <param name="outputPoint" type=""></param>

            var a = tedPoint.curve.a.slice(),
                d = tedPoint.curve.d.slice(),
                p = tedPoint.curve.p,
                modMul = cryptoMath.modMul,
                modInv = cryptoMath.modInv;

            // t1 = 5
            temp1 = [5];

            // t2 = 5a
            modMul(a, temp1, p, temp2);

            // t2 = 5a-d
            modSub(temp2, d, temp2);

            // t3 = 5d
            modMul(d, temp1, p, temp3);

            // t1 = a-5d
            modSub(a, temp3, temp1);

            // t3 = yTE*(a-5d)
            modMul(tedPoint.y, temp1, p, temp3);

            // t2 = (5a-d) + yTE*(a-5d)
            modAdd(temp3, temp2, temp2);

            // t1 = 1
            temp1 = [1];

            // t3 = 1-yTE
            modSub(temp1, tedPoint.y, temp3);

            // t1 = 12
            temp1 = [12];

            // t4 = 12(1-yTE)
            modMul(temp1, temp3, p, temp4);

            // t4 = 1/12(1-yTE)
            modInv(temp4, p, temp4, true);

            // t1 = xTE*(1-yTE)
            modMul(tedPoint.x, temp3, p, temp1);

            // t3 = 2xTE*(1-yTE)
            modAdd(temp1, temp1, temp3);

            // t3 = 4xTE*(1-yTE)
            modAdd(temp3, temp3, temp3);

            // t3 = 1/4xTE*(1-yTE)
            modInv(temp3, p, temp3, true);

            // Xfinal = ((5a-d) + yTE*(a-5d))/12(1-yTE)
            modMul(temp4, temp2, p, wPoint.x);

            // t1 = 1
            temp1 = [1];

            // t1 = yTE+1
            modAdd(tedPoint.y, temp1, temp1);

            // t2 = a-d
            modSub(a, d, temp2);

            // t4 = (a-d)*(yTE+1)
            modMul(temp1, temp2, p, temp4);

            // Yfinal = ((a-d)*(yTE+1))/4xTE*(1-yTE)
            modMul(temp4, temp3, p, wPoint.y);

            return;
        }

        function convertWeierstrassToTed(wPoint, tedPoint) {

            var a = tedPoint.curve.a.slice(),
                d = tedPoint.curve.d.slice(),
                p = tedPoint.curve.p,
                modMul = cryptoMath.modMul,
                modInv = cryptoMath.modInv;

            modAdd(wPoint.x, wPoint.x, temp1);

            modAdd(wPoint.x, temp1, temp1);

            // t1 = 6xW
            modAdd(temp1, temp1, temp1);

            // t2 = 6xW - a
            modSub(temp1, a, temp2);

            // t2 = 6xW - a - d
            modSub(temp2, d, temp2);

            modAdd(wPoint.y, wPoint.y, temp3);

            modAdd(wPoint.y, temp3, temp3);

            // t3 = 6yW
            modAdd(temp3, temp3, temp3);

            // t3 = 1/6yW
            modInv(temp3, p, temp3, true);

            // Xfinal = (6xW - a - d)/6yW
            modMul(temp2, temp3, p, tedPoint.x);

            // t1 = 12xW
            modAdd(temp1, temp1, temp1);

            // t2 = 12xW + d
            modAdd(temp1, d, temp2);

            // t1 = 12xW + a
            modAdd(temp1, a, temp1);

            modAdd(a, a, temp3);

            // t2 = 12xW + d - 2a
            modSub(temp2, temp3, temp2);

            // t2 = 12xW + d - 4a
            modSub(temp2, temp3, temp2);

            // t2 = 12xW + d - 5a
            modSub(temp2, a, temp2);

            modAdd(d, d, temp3);

            // t1 = 12xW + a - 2d
            modSub(temp1, temp3, temp1);

            // t1 = 12xW + a - 4d
            modSub(temp1, temp3, temp1);

            // t1 = 12xW + a - 5d
            modSub(temp1, d, temp1);

            // t1 = 1/(12xW + a - 5d)
            modInv(temp1, p, temp1, true);

            // Yfinal = (12xW + d - 5a)/(12xW + a - 5d)
            modMul(temp1, temp2, p, tedPoint.y);

            return;
        }

        var methods = {

            convertToMontgomeryForm: convertToMontgomeryForm,

            convertToStandardForm: convertToStandardForm,

            convertToAffineForm: convertToAffineForm,

            convertToJacobianForm: convertToJacobianForm,

            // For tests
            generatePrecomputationTable: function (w, generatorPoint) {
                /// <param name="w" type="Number"/>
                /// <param name="generatorPoint" type="EllipticCurvePointFp"/>

                return generatePrecomputationTable(w, generatorPoint);
            }

        };

        if (tedCurve) {

            methods.double = doubleTed;
            methods.add = addTed;
            methods.scalarMultiply = scalarMultiply;
            methods.normalize = normalizeTed;
            methods.convertToExtendedProjective = convertToExtendedProjective;
            methods.convertTedToWeierstrass = convertTedToWeierstrass;
            methods.convertWeierstrassToTed = convertWeierstrassToTed;
            methods.generatePrecomputationTable = function (w, generatorPoint) {
                /// <param name="w" type="Number"/>
                /// <param name="generatorPoint" type="EllipticCurvePointFp"/>

                return generatePrecomputationTableTed(w, generatorPoint);
            };
        } else {

            methods.double = double;
            methods.mixedDoubleAdd = mixedDoubleAdd;
            methods.mixedAdd = mixedAdd;
            methods.scalarMultiply = scalarMultiply;
            methods.negate = negate;
        }

        return methods;

    };

    var sec1EncodingFp = function () {
        return {
            encodePoint: function ( point) {
                /// <summary>Encode an EC point without compression.
                /// This function encodes a given points into a bytes array containing 0x04 | X | Y, where X and Y are big endian bytes of x and y coordinates.</summary>
                /// <param name="point" type="EllipticCurvePointFp">Input EC point to encode.</param>
                /// <returns type="Array">A bytes array containing 0x04 | X | Y, where X and Y are big endian encoded x and y coordinates.</returns>

                if (!point) {
                    throw new Error("point");
                }

                if (!point.isAffine) {
                    throw new Error("Point must be in affine form.");
                }

                if (point.isInMontgomeryForm) {
                    throw new Error("Point must not be in Montgomery form.");
                }

                if (point.isInfinity) {
                    return createArray(1); /* [0] */
                } else {
                    var xOctetString = cryptoMath.digitsToBytes(point.x);
                    var yOctetString = cryptoMath.digitsToBytes(point.y);
                    var pOctetString = cryptoMath.digitsToBytes(point.curve.p);     // just to get byte length of p
                    var mlen = pOctetString.length;
                    if (mlen < xOctetString.length || mlen < yOctetString.length) {
                        throw new Error("Point coordinate(s) are bigger than the field order.");
                    }
                    var output = createArray(2 * mlen + 1);       // for encoded x and y

                    output[0] = 0x04;
                    var offset = mlen - xOctetString.length;
                    for (var i = 0; i < xOctetString.length; i++) {
                        output[i + 1 + offset] = xOctetString[i];
                    }
                    offset = mlen - yOctetString.length;
                    for (i = 0; i < yOctetString.length; i++) {
                        output[mlen + i + 1 + offset] = yOctetString[i];
                    }

                    return output;
                }

            },
            decodePoint: function (encoded, curve) {
                /// <param name="encoded" type="Digits"/>
                /// <param name="curve" type="EllipticCurveFp"/>

                if (encoded.length < 1) {
                    throw new Error("Byte array must have non-zero length");
                }

                var pOctetString = cryptoMath.digitsToBytes(curve.p);
                var mlen = pOctetString.length;

                if (encoded[0] === 0x0 && encoded.length === 1) {
                    return curve.createPointAtInfinity();
                } else if (encoded[0] === 0x04 && encoded.length === 1 + 2 * mlen) {
                    // Standard encoding.
                    // Each point is a big endian string of bytes of length.
                    //      'ceiling(log_2(Q)/8)'
                    // Zero-padded and representing the magnitude of the coordinate.
                    var xbytes = createArray(mlen);
                    var ybytes = createArray(mlen);

                    for (var i = 0; i < mlen; i++) {
                        xbytes[i] = encoded[i + 1];
                        ybytes[i] = encoded[mlen + i + 1];
                    }

                    var x = cryptoMath.bytesToDigits(xbytes);
                    var y = cryptoMath.bytesToDigits(ybytes);

                    return EllipticCurvePointFp(curve, false, x, y);
                } else {
                    // We don't support other encoding features such as compression
                    throw new Error("Unsupported encoding format");
                }
            }
        };
    };

    var ModularSquareRootSolver = function (modulus) {
        /// <param name="modulus" type="Digits"/>

        // The modulus we are going to use.
        var p = modulus;

        // Special-K not just for breakfast anymore! This is k = (p-3)/4 + 1
        // which is used for NIST curves (or any curve of with P= 3 mod 4).
        // This field is null if p is not of the special form, or k if it is.
        var specialK = [];

        if (typeof modulus === "undefined") {
            throw new Error("modulus");
        }

        // Support for odd moduli, only.
        if (cryptoMath.isEven(modulus)) {
            throw new Error("Only odd moduli are supported");
        }

        // A montgomery multiplier object for doing fast squaring.
        var mul = cryptoMath.MontgomeryMultiplier(p);

        // 'p === 3 mod 4' then we can use the special super fast version.
        // Otherwise we must use the slower general case algorithm.
        if (p[0] % 4 === 3) {
            // 'special k = (p + 1) / 4'
            cryptoMath.add(p, cryptoMath.One, specialK);
            cryptoMath.shiftRight(specialK, specialK, 2);
        } else {
            specialK = null;
        }

        // Temp storage
        var temp0 = new Array(p.length);
        var temp1 = new Array(p.length);

        function squareRootNistCurves(a) {
            /// <summary>Given a number a, returns a solution x to x^2 = a (mod p).</summary>
            /// <param name="a" type="Array">An integer a.</param>
            /// <returns type="Array">The square root of the number a modulo p, if it exists,
            /// otherwise null.</returns>

            // beta = a^k mod n where k=(n+1)/4 for n == 3 mod 4, thus a^(1/2) mod n
            var beta = cryptoMath.intToDigits(0, 16);
            mul.modExp(a, specialK, beta);

            // Okay now we gotta double check by squaring.
            var aPrime = [0];
            cryptoMath.modMul(beta, beta, mul.m, aPrime);

            // If a != x^2 then a has no square root
            if (cryptoMath.compareDigits(a, aPrime) !== 0) {
                return null;
            }

            return beta;
        }

        var publicMethods = {

            squareRoot: function (a) {
                if (specialK !== null) {
                    // Use the special case fast code
                    return squareRootNistCurves(a);
                } else {
                    // Use the general case code
                    throw new Error("GeneralCase not supported.");
                }
            },

            // Given an integer a, this routine returns the Jacobi symbol (a/p),
            // where p is the modulus given in the constructor, which for p an
            // odd prime is also the Legendre symbol. From "Prime Numbers, A
            // Computational Perspective" by Crandall and Pomerance, alg. 2.3.5.
            // The Legendre symbol is defined as:
            //   0   if a === 0 mod p.
            //   1   if a is a quadratic residue (mod p).
            //   -1  if a is a quadratic non-reside (mod p).
            jacobiSymbol: function (a) {
                /// <param name="a">An integer a.</param>

                var modEightMask = 0x7,
                    modFourMask = 0x3,
                    aPrime,
                    pPrime;

                // Clone our inputs, we are going to destroy them
                aPrime = a.slice();
                pPrime = p.slice();

                // 'a = a mod p'.
                cryptoMath.reduce(aPrime, pPrime, aPrime, temp0, temp1);

                // 't = 1'
                var t = 1;

                // While (a != 0)
                while (!cryptoMath.isZero(aPrime)) {
                    // While a is even
                    while (cryptoMath.isEven(aPrime)) {
                        // 'a <- a / 2'
                        cryptoMath.shiftRight(aPrime, aPrime);

                        // If (p mod 8 in {3,5}) t = -t;
                        var pMod8 = (pPrime[0] & modEightMask);
                        if (pMod8 === 3 || pMod8 === 5) {
                            t = -t;
                        }
                    }

                    // Swap variables
                    // (a, p) = (p, a).
                    var tmp = aPrime;
                    aPrime = pPrime;
                    pPrime = tmp;

                    // If (a === p === 3 (mod 4)) t = -t;
                    var aMod4 = (aPrime[0] & modFourMask);
                    var pMod4 = (pPrime[0] & modFourMask);
                    if (aMod4 === 3 && pMod4 === 3) {
                        t = -t;
                    }

                    // 'a = a mod p'
                    cryptoMath.reduce(aPrime, pPrime, aPrime, temp0, temp1);
                }

                // If (p == 1) return t else return 0
                if (cryptoMath.compareDigits(pPrime, cryptoMath.One) === 0) {
                    return t;
                } else {
                    return 0;
                }
            }

        };

        return publicMethods;
    };

    var curvesInternal = {};

    var createCurve = function (curveName) {

        var curveData = curvesInternal[curveName.toUpperCase()];

        if (!curveData) {
            throw new Error(curveName + " Unsupported curve.");
        }

        if (curveData.type === 0) {
            return createWeierstrassCurve(curveData);
        }

        if (curveData.type === 1) {
            return createTedCurve(curveData);
        }

        throw new Error(curveName + " Unsupported curve type.");
    };

    return {
        createCurve: createCurve,
        curves: curvesInternal,
        sec1EncodingFp: sec1EncodingFp,
        EllipticCurvePointFp: EllipticCurvePointFp,
        EllipticCurveOperatorFp: EllipticCurveOperatorFp,
        ModularSquareRootSolver: ModularSquareRootSolver
    };
}

var cryptoECC = cryptoECC || MsrcryptoECC();

var curve_P256 = {
    name: "P-256",
    type: 0, // Curve Type 0 = Weierstrass, 1 Twisted Edwards
    p: [0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF],
    a: [0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC],
    b: [0x5A, 0xC6, 0x35, 0xD8, 0xAA, 0x3A, 0x93, 0xE7, 0xB3, 0xEB, 0xBD, 0x55, 0x76, 0x98, 0x86, 0xBC, 0x65, 0x1D, 0x06, 0xB0, 0xCC, 0x53, 0xB0, 0xF6, 0x3B, 0xCE, 0x3C, 0x3E, 0x27, 0xD2, 0x60, 0x4B],
    order: [0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xBC, 0xE6, 0xFA, 0xAD, 0xA7, 0x17, 0x9E, 0x84, 0xF3, 0xB9, 0xCA, 0xC2, 0xFC, 0x63, 0x25, 0x51],
    gx: [0x6B, 0x17, 0xD1, 0xF2, 0xE1, 0x2C, 0x42, 0x47, 0xF8, 0xBC, 0xE6, 0xE5, 0x63, 0xA4, 0x40, 0xF2, 0x77, 0x03, 0x7D, 0x81, 0x2D, 0xEB, 0x33, 0xA0, 0xF4, 0xA1, 0x39, 0x45, 0xD8, 0x98, 0xC2, 0x96],
    gy: [0x4F, 0xE3, 0x42, 0xE2, 0xFE, 0x1A, 0x7F, 0x9B, 0x8E, 0xE7, 0xEB, 0x4A, 0x7C, 0x0F, 0x9E, 0x16, 0x2B, 0xCE, 0x33, 0x57, 0x6B, 0x31, 0x5E, 0xCE, 0xCB, 0xB6, 0x40, 0x68, 0x37, 0xBF, 0x51, 0xF5],
    cf: 1  // co-factor
};

var curve_P384 = {
    name: "P-384",
    type: 0, // Curve Type 0 = Weierstrass, 1 Twisted Edwards
    p: [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF],
    a: [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFC],
    b: [0xB3, 0x31, 0x2F, 0xA7, 0xE2, 0x3E, 0xE7, 0xE4, 0x98, 0x8E, 0x05, 0x6B, 0xE3, 0xF8, 0x2D, 0x19, 0x18, 0x1D, 0x9C, 0x6E, 0xFE, 0x81, 0x41, 0x12, 0x03, 0x14, 0x08, 0x8F, 0x50, 0x13, 0x87, 0x5A, 0xC6, 0x56, 0x39, 0x8D, 0x8A, 0x2E, 0xD1, 0x9D, 0x2A, 0x85, 0xC8, 0xED, 0xD3, 0xEC, 0x2A, 0xEF],
    order: [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC7, 0x63, 0x4D, 0x81, 0xF4, 0x37, 0x2D, 0xDF, 0x58, 0x1A, 0x0D, 0xB2, 0x48, 0xB0, 0xA7, 0x7A, 0xEC, 0xEC, 0x19, 0x6A, 0xCC, 0xC5, 0x29, 0x73],
    gx: [0xAA, 0x87, 0xCA, 0x22, 0xBE, 0x8B, 0x05, 0x37, 0x8E, 0xB1, 0xC7, 0x1E, 0xF3, 0x20, 0xAD, 0x74, 0x6E, 0x1D, 0x3B, 0x62, 0x8B, 0xA7, 0x9B, 0x98, 0x59, 0xF7, 0x41, 0xE0, 0x82, 0x54, 0x2A, 0x38, 0x55, 0x02, 0xF2, 0x5D, 0xBF, 0x55, 0x29, 0x6C, 0x3A, 0x54, 0x5E, 0x38, 0x72, 0x76, 0x0A, 0xB7],
    gy: [0x36, 0x17, 0xDE, 0x4A, 0x96, 0x26, 0x2C, 0x6F, 0x5D, 0x9E, 0x98, 0xBF, 0x92, 0x92, 0xDC, 0x29, 0xF8, 0xF4, 0x1D, 0xBD, 0x28, 0x9A, 0x14, 0x7C, 0xE9, 0xDA, 0x31, 0x13, 0xB5, 0xF0, 0xB8, 0xC0, 0x0A, 0x60, 0xB1, 0xCE, 0x1D, 0x7E, 0x81, 0x9D, 0x7A, 0x43, 0x1D, 0x7C, 0x90, 0xEA, 0x0E, 0x5F],
    cf: 1  // co-factor
};

var curve_P521 = {
    name: "P-521",
    type: 0, // Curve Type 0 = Weierstrass, 1 Twisted Edwards
    p: [0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF],
    a: [0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC],
    b: [0x00, 0x51, 0x95, 0x3E, 0xB9, 0x61, 0x8E, 0x1C, 0x9A, 0x1F, 0x92, 0x9A, 0x21, 0xA0, 0xB6, 0x85, 0x40, 0xEE, 0xA2, 0xDA, 0x72, 0x5B, 0x99, 0xB3, 0x15, 0xF3, 0xB8, 0xB4, 0x89, 0x91, 0x8E, 0xF1, 0x09, 0xE1, 0x56, 0x19, 0x39, 0x51, 0xEC, 0x7E, 0x93, 0x7B, 0x16, 0x52, 0xC0, 0xBD, 0x3B, 0xB1, 0xBF, 0x07, 0x35, 0x73, 0xDF, 0x88, 0x3D, 0x2C, 0x34, 0xF1, 0xEF, 0x45, 0x1F, 0xD4, 0x6B, 0x50, 0x3F, 0x00],
    order: [0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFA, 0x51, 0x86, 0x87, 0x83, 0xBF, 0x2F, 0x96, 0x6B, 0x7F, 0xCC, 0x01, 0x48, 0xF7, 0x09, 0xA5, 0xD0, 0x3B, 0xB5, 0xC9, 0xB8, 0x89, 0x9C, 0x47, 0xAE, 0xBB, 0x6F, 0xB7, 0x1E, 0x91, 0x38, 0x64, 0x09],
    gx: [0x00, 0xC6, 0x85, 0x8E, 0x06, 0xB7, 0x04, 0x04, 0xE9, 0xCD, 0x9E, 0x3E, 0xCB, 0x66, 0x23, 0x95, 0xB4, 0x42, 0x9C, 0x64, 0x81, 0x39, 0x05, 0x3F, 0xB5, 0x21, 0xF8, 0x28, 0xAF, 0x60, 0x6B, 0x4D, 0x3D, 0xBA, 0xA1, 0x4B, 0x5E, 0x77, 0xEF, 0xE7, 0x59, 0x28, 0xFE, 0x1D, 0xC1, 0x27, 0xA2, 0xFF, 0xA8, 0xDE, 0x33, 0x48, 0xB3, 0xC1, 0x85, 0x6A, 0x42, 0x9B, 0xF9, 0x7E, 0x7E, 0x31, 0xC2, 0xE5, 0xBD, 0x66],
    gy: [0x01, 0x18, 0x39, 0x29, 0x6A, 0x78, 0x9A, 0x3B, 0xC0, 0x04, 0x5C, 0x8A, 0x5F, 0xB4, 0x2C, 0x7D, 0x1B, 0xD9, 0x98, 0xF5, 0x44, 0x49, 0x57, 0x9B, 0x44, 0x68, 0x17, 0xAF, 0xBD, 0x17, 0x27, 0x3E, 0x66, 0x2C, 0x97, 0xEE, 0x72, 0x99, 0x5E, 0xF4, 0x26, 0x40, 0xC5, 0x50, 0xB9, 0x01, 0x3F, 0xAD, 0x07, 0x61, 0x35, 0x3C, 0x70, 0x86, 0xA2, 0x72, 0xC2, 0x40, 0x88, 0xBE, 0x94, 0x76, 0x9F, 0xD1, 0x66, 0x50],
    cf: 1  // co-factor
};

if (typeof cryptoECC !== 'undefined') {
    // Add curves to ECC object
    cryptoECC.curves["P-256"] = curve_P256;
    cryptoECC.curves["P-384"] = curve_P384;
    cryptoECC.curves["P-521"] = curve_P521;
}

var curve_BN254 = {
    name: "BN-254",
    type: 0, // Curve Type 0 = Weierstrass, 1 Twisted Edwards
    p: [0x25, 0x23, 0x64, 0x82, 0x40, 0x00, 0x00, 0x01, 0xBA, 0x34, 0x4D, 0x80, 0x00, 0x00, 0x00, 0x08, 0x61, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0xA7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13],
    a: [0x00],
    b: [0x02],
    order: [0x25, 0x23, 0x64, 0x82, 0x40, 0x00, 0x00, 0x01, 0xBA, 0x34, 0x4D, 0x80, 0x00, 0x00, 0x00, 0x07, 0xFF, 0x9F, 0x80, 0x00, 0x00, 0x00, 0x00, 0x10, 0xA1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0D],
    gx: [0x25, 0x23, 0x64, 0x82, 0x40, 0x00, 0x00, 0x01, 0xBA, 0x34, 0x4D, 0x80, 0x00, 0x00, 0x00, 0x08, 0x61, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0xA7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12],
    gy: [0x01],
    cf: 1  // co-factor
};

if (typeof cryptoECC !== 'undefined') {
    // Add curves to ECC object
    cryptoECC.curves["BN-254"] = curve_BN254;
}

//
// "numsp256d1": Weierstrass curve a=-3, E: y^2 = x^3 - 3x + 152961, p = 2^256-189
//
var curve_numsp256d1 = {
    // Curve ID, 2 x targeted security level, order bitlength, prime bitlength
    info: ['numsp256d1', 256, 256, 256],
    // Curve Type 0 = Weierstrass, 1 Twisted Edwards
    type: 0,
    // Prime p = 2^256-189
    p: [0x43, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF].reverse(),
    // Parameter "a"
    a: [0x40, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF].reverse(),
    // Parameter "b"
    b: [0x81, 0x55, 0x02].reverse(),
    // Order of the group
    order: [0x25, 0xa8, 0x51, 0x47, 0x29, 0x20, 0xab, 0x20, 0x60, 0x5c, 0x26, 0xea, 0x75, 0x82, 0x3c, 0xe4, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff].reverse(),
    // x(generator)
    gx: [0xB1, 0xAC, 0x1A, 0xB2, 0x1E, 0xEE, 0x52, 0xBC, 0x3A, 0xC7, 0xD4, 0x03, 0x09, 0x9B, 0x57, 0x83, 0x09, 0xCB, 0x42, 0x4F, 0xA0, 0x95, 0x7A, 0x29, 0x61, 0xDB, 0xAA, 0x5A, 0xB6, 0xD6, 0x9E, 0xBC].reverse(),
    // y(generator)
    gy: [0x9F, 0xDE, 0x84, 0x21, 0xCB, 0xB9, 0xB5, 0x80, 0xBB, 0x0F, 0x31, 0x15, 0xD1, 0xC3, 0x55, 0xC9, 0x35, 0xE0, 0x04, 0x7E, 0xF7, 0x8B, 0x44, 0x73, 0xA6, 0xB6, 0x99, 0x33, 0xF1, 0xC0, 0x8F, 0xD0].reverse(),
    // co-factor
    cf: 1
};

//
// "numsp256t1": twisted Edwards curve a=1, E: x^2 + y^2 = 1 - 15342x^2y^2, p = 2^256-189
//
var curve_numsp256t1 = {
    // Curve ID, 2 x targeted security level, order bitlength, prime bitlength
    info: ['numsp256t1', 256, 255, 256],
    // Name
    name: "numsp256t1",
    // Curve Type 0 = Weierstrass, 1 Twisted Edwards
    type: 1,
    // Prime p = 2^256-189
    p: [0x43, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF].reverse(),
    // Parameter "a"
    a: [0x01],
    // Parameter "d"
    d: [0x55, 0xC3, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF].reverse(),
    // Order of the subgroup
    order: [0xF5, 0x4A, 0xDD, 0xEE, 0x90, 0xB1, 0x47, 0x1A, 0x9B, 0x43, 0x59, 0x2F, 0xA5, 0x5A, 0x95, 0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40].reverse(),
    // x(generator)
    gx: [0xDA, 0x13, 0xED, 0x2E, 0x90, 0xC0, 0xDE, 0xA0, 0x86, 0x35, 0x08, 0xE3, 0x0E, 0x8A, 0x39, 0x0C, 0xD6, 0x9B, 0x20, 0x69, 0x5F, 0x3D, 0x1E, 0xCD, 0x7D, 0x23, 0xEA, 0x6A, 0xFB, 0x14, 0x75, 0x8A].reverse(),
    // y(generator)
    gy: [0xE6, 0x89, 0x8A, 0x79, 0xE7, 0x16, 0xA6, 0x2F, 0xD3, 0x6E, 0x85, 0x10, 0xD8, 0x61, 0x5F, 0x71, 0x10, 0x80, 0x4B, 0xA6, 0xD9, 0x65, 0x96, 0xCE, 0xC7, 0x25, 0xD9, 0xD9, 0x9F, 0x3E, 0xD5, 0x44].reverse(),
    // co-factor
    cf: 4
};

//
// "numsp384d1": Weierstrass curve a=-3, E: y^2 = x^3 - 3x - 34568, p = 2^384-317
//
var curve_numsp384d1 = {
    // Curve ID, 2 x targeted security level, order bitlength, prime bitlength
    info: ['numsp384d1', 384, 384, 384],
    // Name
    name: "numsp384d1",
    // Curve Type 0 = Weierstrass, 1 Twisted Edwards
    type: 0,
    // Prime p = 2^384-317
    p: [0xC3, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
      0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF].reverse(),
    // Parameter "a"
    a: [0xC0, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
        0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF].reverse(),
    // Parameter "b"
    b: [0xBB, 0x77, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
        0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF].reverse(),
    // Order of the group
    order: [0xb9, 0x61, 0x0e, 0x7b, 0xf6, 0x81, 0x4d, 0x60, 0x7a, 0xe2, 0x37, 0x4c, 0x3d, 0x9d, 0xda, 0xbe, 0x81, 0x68, 0x5d, 0xeb, 0x1e, 0xaf, 0x1e, 0xd6, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff].reverse(),
    // x(generator)
    gx: [0x2A, 0x15, 0x98, 0x20, 0x04, 0xBA, 0x9C, 0xEB, 0x7B, 0xC4, 0x61, 0x0F, 0x10, 0xED, 0x2E, 0x52, 0x42, 0xC7, 0x6C, 0x2A, 0x1B, 0x29, 0xBD, 0xF3, 0xF4, 0xF9, 0x81, 0xFB, 0xCD, 0xC1, 0x25, 0x02,
          0xA6, 0xF1, 0x05, 0x41, 0x22, 0xCA, 0x80, 0x48, 0x1C, 0x18, 0x6F, 0xB1, 0xF0, 0x56, 0x79, 0x75].reverse(),
    // y(generator)
    gy: [0x16, 0x07, 0x18, 0x66, 0xEC, 0xB8, 0x74, 0x5C, 0x26, 0xAD, 0xF4, 0xBF, 0xDB, 0xB4, 0xD6, 0xBC, 0x7E, 0x83, 0x1A, 0x12, 0x7D, 0x83, 0x20, 0xB9, 0x9C, 0x73, 0x7F, 0xF8, 0x77, 0x69, 0x04, 0xB0,
        0x7E, 0xCF, 0x84, 0x05, 0x30, 0x3D, 0xE3, 0xD7, 0x38, 0x8E, 0x9B, 0xE1, 0x68, 0xE3, 0xDE, 0xAC].reverse(),
    // co-factor
    cf: 1
};

//
// "numsp384t1": twisted Edwards curve a=1, E: x^2 + y^2 = 1 - 11556x^2y^2, p = 2^384-317
//
var curve_numsp384t1 = {
    // Curve ID, 2 x targeted security level, order bitlength, prime bitlength
    info: ['numsp384t1', 384, 382, 384],
    // Name
    name: "numsp384t1",
    // Curve Type 0 = Weierstrass, 1 Twisted Edwards
    type: 1,
    // Prime p = 2^2^384-317
    p: [ 0xC3, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
          0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF ].reverse(),
    // Parameter "a"
    a: [ 0x01 ],
    // Parameter "d"
    d: [ 0x9F, 0xD1, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
          0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF ].reverse(),
    // Order of the subgroup
    order: [ 0x7D, 0x89, 0xA3, 0xE6, 0xC4, 0xDC, 0xB9, 0x20, 0x79, 0xC8, 0x35, 0xAB, 0x5A, 0x55, 0xE4, 0x61, 0xCF, 0xE1, 0x6B, 0xB4, 0x1C, 0x1A, 0x47, 0xE2, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
        0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x3F ].reverse(),
    // x(generator)
    gx: [ 0xDE, 0x6B, 0x20, 0x6C, 0xE4, 0x40, 0xD5, 0x50, 0x13, 0x94, 0x45, 0x65, 0xB1, 0x92, 0xF2, 0x6F, 0x40, 0x63, 0x31, 0xF3, 0xA8, 0xFF, 0x63, 0x57, 0x00, 0x4C, 0xBE, 0xE5, 0x46, 0xF4, 0x0B, 0xB3,
          0xB5, 0x5D, 0xE5, 0x9A, 0x12, 0xA2, 0xB6, 0xC0, 0x6C, 0x26, 0xA9, 0x45, 0xFB, 0x11, 0xB1, 0x61 ].reverse(),
    // y(generator)
    gy: [ 0x92, 0x93, 0x72, 0xF0, 0xE1, 0x03, 0x8D, 0x9D, 0xDC, 0x48, 0xEC, 0x46, 0xF9, 0xB0, 0x72, 0x00, 0x4B, 0x96, 0x45, 0xF6, 0xF7, 0x98, 0x0F, 0x83, 0x56, 0x5F, 0x42, 0xF1, 0x74, 0x82, 0xAD, 0x16,
        0xD7, 0x0D, 0xB1, 0x23, 0xA4, 0xB1, 0x38, 0x87, 0xB0, 0xEE, 0xA6, 0xB9, 0x67, 0x3E, 0x98, 0x82 ].reverse(),
    // co-factor
    cf: 4
};

//
// "numsp512d1": Weierstrass curve a=-3, E: y^2 = x^3 - 3x + 121243, p = 2^512-569
//
var curve_numsp512d1 = {
    // Curve ID, 2 x targeted security level, order bitlength, prime bitlength
    info: ['numsp512d1', 512, 512, 512],
    // Name
    name: "numsp512d1",
    // Curve Type 0 = Weierstrass, 1 Twisted Edwards
    type: 0,
    // Prime p = 2^512-569
    p: [0xC7, 0xFD, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
        0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF].reverse(),
    // Parameter "a"
    a: [0xC4, 0xFD, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
        0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF].reverse(),
    // Parameter "b"
    b: [0x9B, 0xD9, 0x01].reverse(),
    // Order of the group
    order: [0x5d, 0x55, 0x33, 0x04, 0x39, 0x3f, 0x15, 0xce, 0x43, 0xd2, 0x7c, 0x60, 0x36, 0x8b, 0x56, 0x3b, 0xc6, 0xbd, 0xd0, 0x97, 0xed, 0x58, 0xc2, 0x4f, 0x1b, 0x83, 0xe7, 0x94, 0xfb, 0xa4, 0x3c, 0x5b,
        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff].reverse(),
    // x(generator)
    gx: [0x57, 0xAE, 0xAB, 0x8C, 0x95, 0x87, 0x82, 0xDC, 0xE2, 0x5D, 0x6F, 0x7D, 0x13, 0x60, 0x5D, 0x1D, 0x83, 0x15, 0x56, 0x25, 0x86, 0x42, 0x79, 0x93, 0x9E, 0x35, 0x6B, 0x07, 0x51, 0xA1, 0x21, 0x50,
        0xF9, 0xD9, 0x06, 0x53, 0xC2, 0xE0, 0x06, 0x45, 0x85, 0xF6, 0x01, 0xB5, 0x3B, 0xD8, 0xCA, 0x98, 0x52, 0x3B, 0x3D, 0xA0, 0x02, 0x70, 0x2B, 0xDA, 0x93, 0x0A, 0x1D, 0x14, 0x47, 0x34, 0xC0, 0x3A].reverse(),
    // y(generator)
    gy: [0xA6, 0x27, 0x35, 0x38, 0x60, 0x87, 0xA0, 0x23, 0xE9, 0x0F, 0xFD, 0x4C, 0x1E, 0x5C, 0x2B, 0xCF, 0x02, 0x56, 0x5A, 0xB2, 0x40, 0xA8, 0x21, 0xC1, 0xE9, 0xED, 0x0E, 0x8B, 0xDA, 0x15, 0x84, 0xA2,
        0x14, 0x4F, 0xD1, 0x7B, 0x0C, 0x26, 0x4B, 0x8F, 0x8C, 0xBB, 0xBC, 0xAB, 0xDE, 0xDB, 0x97, 0x4B, 0x00, 0xB1, 0xEB, 0x63, 0xDC, 0xEE, 0x0E, 0xCE, 0xB3, 0x56, 0xAD, 0x29, 0xCA, 0x54, 0x3A, 0x94].reverse(),
    // co-factor
    cf: 4
}

//
// "numsp512t1": twisted Edwards curve a=1, E: x^2 + y^2 = 1 - 78296x^2y^2, p = 2^512-569
//
var curve_numsp512t1 = {
    // Curve ID, , 2 x targeted security level, order bitlength, prime bitlength
    info: ['numsp512t1', 512, 510, 512],
    // Name
    name: "numsp512t1",
    // Curve Type 0 = Weierstrass, 1 Twisted Edwards
    type: 1,
    // Prime p = 2^512-569
    p: [0xC7, 0xFD, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
        0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF].reverse(),
    // Parameter "a"
    a: [0x01].reverse(),
    // Parameter "d"
    d: [0xEF, 0xCB, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
        0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF].reverse(),
    // Order of the subgroup
    order: [0x6D, 0xD4, 0xEE, 0x1B, 0xF5, 0x8C, 0x46, 0x67, 0xFF, 0xEC, 0xEF, 0x6D, 0x78, 0x05, 0x46, 0x2A, 0xF5, 0x86, 0xB6, 0x70, 0xC9, 0xD8, 0x3F, 0x9E, 0xBA, 0x91, 0xCF, 0x2F, 0x6D, 0x63, 0xF0, 0xB4,
        0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x3F].reverse(),
    // x(generator)
    gx: [0xFE, 0x57, 0xEC, 0x99, 0x29, 0xAB, 0xB9, 0xC5, 0x15, 0xF0, 0xC4, 0x7C, 0x42, 0x25, 0xE5, 0x0F, 0xAD, 0x04, 0x89, 0x56, 0x92, 0xC9, 0xBD, 0x78, 0x0F, 0x73, 0x46, 0xEE, 0x4E, 0xC1, 0x21, 0x46,
        0x47, 0x81, 0x3B, 0x27, 0xBE, 0x7E, 0xA1, 0x27, 0x82, 0xA3, 0xC4, 0x4D, 0x9F, 0xE7, 0xD1, 0x2F, 0x33, 0xC5, 0xD3, 0x88, 0x78, 0xCB, 0x18, 0x7A, 0x9C, 0xB6, 0x8D, 0x12, 0x6D, 0x31, 0x8E, 0xDF].reverse(),
    // y(generator)
    gy: [0xE1, 0xF5, 0xE2, 0xC1, 0xC0, 0xDE, 0x6D, 0x32, 0x1F, 0xD0, 0xF1, 0x9B, 0x8A, 0xD3, 0x66, 0x02, 0xFD, 0xC1, 0xEC, 0x2A, 0x86, 0x06, 0x1A, 0x60, 0x62, 0x35, 0x96, 0xE9, 0xF2, 0x53, 0xCA, 0x20,
        0x41, 0x83, 0x9E, 0x90, 0x95, 0x6B, 0x2B, 0xA9, 0x22, 0x9D, 0x25, 0xD8, 0x26, 0xF7, 0x76, 0xE4, 0x6E, 0x25, 0x2A, 0xA8, 0x77, 0xF5, 0xB0, 0x98, 0x71, 0xCA, 0x49, 0x9D, 0xF3, 0xBF, 0x09, 0x6D].reverse(),
    // co-factor
    cf: 4
}

if (typeof cryptoECC !== 'undefined') {
    // Add curves to ECC object - use uppercase names
    cryptoECC.curves["NUMSP256D1"] = curve_numsp256d1;
    cryptoECC.curves["NUMSP384D1"] = curve_numsp384d1;
    cryptoECC.curves["NUMSP512D1"] = curve_numsp512t1;
    cryptoECC.curves["NUMSP256T1"] = curve_numsp256t1;
    cryptoECC.curves["NUMSP384T1"] = curve_numsp384t1;
    cryptoECC.curves["NUMSP512T1"] = curve_numsp512t1;
}

function BlockFunctionTypeDef(message, blockIndex, initialHashValues, k, w) {
    /// <signature>
    ///     <summary>
    ///         Type definition for block function
    ///     </summary>
    ///     <param name="message" type="Array">Block data</param>
    ///     <param name="blockIndex" type="Number">Block number to operate on</param>
    ///     <param name="initialHashValues" type="Array"></param>
    ///     <param name="k" type="Array">K constants</param>
    ///     <param name="w" type="Array">Array to hold w values</param>
    ///     <returns type="Array">Hash values</returns>
    /// </signature>

    return [];
}

var msrcryptoSha = function (name, der, h, k, blockBytes, blockFunction, truncateTo) {
    /// <summary>
    /// Returns a hash function using the passed in parameters.
    /// </summary>
    /// <param name="name" type="String">Name of the hash function.</param>
    /// <param name="der" type="Array"></param>
    /// <param name="h" type="Array"></param>
    /// <param name="k" type="Array"></param>
    /// <param name="blockBytes" type="Number">The number of bytes in a block.</param>
    /// <param name="blockFunction" type="BlockFunctionTypeDef">Function for processing blocks.</param>
    /// <param name="truncateTo" type="Number">Truncate the resulting hash to a fixed length.</param>
    /// <returns type="Object"></returns>

    var utils = msrcryptoUtilities;

    // Make a copy of h so we don't alter the initialization array.
    var hv = h.slice(),
        w = new Array(blockBytes),
        buffer = [],
        blocksProcessed = 0;

    function hashBlocks(message) {
        /// <summary>
        /// Breaks a array of data into full blocks and hashes each block in sequence.
        /// Data at the end of the message that does not fill an entire block is
        /// returned.
        /// </summary>
        /// <param name="message" type="Array">Byte data to hash</param>
        /// <returns type="Array">Unprocessed data at the end of the message that did
        /// not fill an entire block.</returns>

        var blockCount = Math.floor(message.length / blockBytes);

        // Process each  block of the message
        for (var block = 0; block < blockCount; block++) {
            blockFunction(message, block, hv, k, w);
        }

        // Keep track of the number of blocks processed.
        // We have to put the total message size into the padding.
        blocksProcessed += blockCount;

        // Return the unprocessed data.
        return message.slice(blockCount * blockBytes);
    }

    function hashToBytes() {
        /// <summary>
        /// Converts stored hash values (32-bit ints) to bytes.
        /// </summary>
        /// <returns type="Array"></returns>

        // Move the results to an uint8 array.
        var hash = [];

        for (var i = 0; i < hv.length; i++) {
            hash = hash.concat(utils.int32ToBytes(hv[i]));
        }

        // Truncate the results depending on the hash algorithm used.
        hash.length = (truncateTo / 8);

        return hash;
    }

    function addPadding(messageBytes) {
        /// <summary>
        /// Builds and appends padding to a message
        /// </summary>
        /// <param name="messageBytes" type="Array">Message to pad</param>
        /// <returns type="Array">The message array + padding</returns>

        var padLen = blockBytes - messageBytes.length % blockBytes;

        // If there is 8 (16 for sha-512) or less bytes of padding, pad an additional block.
        (padLen <= (blockBytes / 8)) && (padLen += blockBytes);

        // Create a new Array that will contain the message + padding
        var padding = utils.getVector(padLen);

        // Set the 1 bit at the end of the message data
        padding[0] = 128;

        // Set the length equal to the previous data len + the new data len
        var messageLenBits = (messageBytes.length + blocksProcessed * blockBytes) * 8;

        // Set the message length in the last 4 bytes
        padding[padLen - 4] = messageLenBits >>> 24 & 255;
        padding[padLen - 3] = messageLenBits >>> 16 & 255;
        padding[padLen - 2] = messageLenBits >>> 8 & 255;
        padding[padLen - 1] = messageLenBits & 255;

        return messageBytes.concat(padding);
    }

    function computeHash(messageBytes) {
        /// <summary>
        /// Computes the hash of an entire message.
        /// </summary>
        /// <param name="messageBytes" type="Array">Byte array to hash</param>
        /// <returns type="Array">Hash of message bytes</returns>

        buffer = hashBlocks(messageBytes);

        return finish();
    }

    function process(messageBytes) {
        /// <summary>
        /// Call process repeatedly to hash a stream of bytes. Then call 'finish' to
        /// complete the hash and get the final result.
        /// </summary>
        /// <param name="messageBytes" type="Array"></param>

        // Append the new data to the buffer (previous unprocessed data)
        buffer = buffer.concat(messageBytes);

        // If there is at least one block of data, hash it
        if (buffer.length >= blockBytes) {

            // The remaining unprocessed data goes back into the buffer
            buffer = hashBlocks(buffer);
        }

        return;
    }

    function finish() {
        /// <summary>
        /// Called after one or more calls to process. This will finalize the hashing
        /// of the 'streamed' data and return the hash.
        /// </summary>
        /// <returns type="Array">Hash of message bytes</returns>

        // All the full blocks of data have been processed. Now we pad the rest and hash.
        // Buffer should be empty now.
        if (hashBlocks(addPadding(buffer)).length !== 0) {
            throw new Error("buffer.length !== 0");
        }

        // Convert the intermediate hash values to bytes
        var result = hashToBytes();

        // Reset the buffer
        buffer = [];

        // Restore the initial hash values
        hv = h.slice();

        // Reset the block counter
        blocksProcessed = 0;

        return result;
    }

    return {
        name: name,
        computeHash: computeHash,
        process: process,
        finish: finish,
        der: der,
        hashLen: truncateTo,
        maxMessageSize: 0xFFFFFFFF // (2^32 - 1 is max array size in JavaScript)
    };

};

/// JS2025.InsertSpaceBeforeCommentText,
/// JS2027.PunctuateCommentsCorrectly,
/// JS2005.UseShortFormInitializations,
/// JS3056.DeclareVariablesOnceOnly,
/// JS2073.CommentIsMisspelled,
/// JS3053.IncorrectNumberOfArguments,
/// JS3058.DeclareVariablesBeforeUse,
/// JS2074.IdentifierNameIsMisspelled,
/// JS3055.InconsistentReturnType
/// </disable>

var msrcryptoSha1 = (function () {

    function hashBlock(message, blockIndex, hv, k, w) {
        /// <summary>
        /// Block function for hashing algorithm to use.
        /// </summary>
        /// <param name="message" type="Array">Block data to hash</param>
        /// <param name="blockIndex" type="Number">The block of the data to hash</param>
        /// <param name="hv" type="Array">Initial hash values</param>
        /// <param name="k" type="Array">K constants</param>
        /// <param name="w" type="Array">Buffer for w values</param>
        /// <returns type="Array">Updated initial hash values</returns>

        var t, i, temp, x0, blockSize = 64, mask = 0xFFFFFFFF;

        var ra = hv[0],
            rb = hv[1],
            rc = hv[2],
            rd = hv[3],
            re = hv[4];

        // 0 ≤ t ≤ 15
        for (i = 0; i < 16; i++) {
            w[i] = utils.bytesToInt32(message, blockIndex * blockSize + i * 4);
        }

        // 16 ≤ t ≤ 79
        for (t = 16; t < 80; t++) {
            x0 = w[t - 3] ^ w[t - 8] ^ w[t - 14] ^ w[t - 16];
            w[t] = (x0 << 1) | (x0 >>> 31);
        }

        for (i = 0; i < 80; i++) {

            // Ch(x, y, z)=(x & y) ^ (~x & z)
            temp = ((ra << 5) | (ra >>> 27));

            temp +=
                i >= 60 ? (rb ^ rc ^ rd) :
                i >= 40 ? ((rb & rc) ^ (rb & rd) ^ (rc & rd)) :
                i >= 20 ? (rb ^ rc ^ rd) :
                /*i<=20*/ ((rb & rc) ^ ((~rb) & rd));

            temp += (re + k[i] + w[i]);

            re = rd;
            rd = rc;
            rc = ((rb << 30) | (rb >>> 2));
            rb = ra;
            ra = temp;
        }

        // Update the hash values
        hv[0] += ra & mask;
        hv[1] += rb & mask;
        hv[2] += rc & mask;
        hv[3] += rd & mask;
        hv[4] += re & mask;

        return hv;
    }

    var utils = msrcryptoUtilities,
        upd = utils.unpackData,
        h = upd("Z0UjAe/Nq4mYutz+EDJUdsPS4fA=", 4, 1),
        k = upd("WoJ5mVqCeZlagnmZWoJ5mVqCeZlagnmZWoJ5mVqCeZlagnmZWoJ5mVqCeZlagnmZWoJ5mVqCeZlagnmZWoJ5mVqCeZlagnmZWoJ5mVqCeZlu2euhbtnroW7Z66Fu2euhbtnroW7Z66Fu2euhbtnroW7Z66Fu2euhbtnroW7Z66Fu2euhbtnroW7Z66Fu2euhbtnroW7Z66Fu2euhbtnroY8bvNyPG7zcjxu83I8bvNyPG7zcjxu83I8bvNyPG7zcjxu83I8bvNyPG7zcjxu83I8bvNyPG7zcjxu83I8bvNyPG7zcjxu83I8bvNyPG7zcymLB1spiwdbKYsHWymLB1spiwdbKYsHWymLB1spiwdbKYsHWymLB1spiwdbKYsHWymLB1spiwdbKYsHWymLB1spiwdbKYsHWymLB1spiwdY", 4, 1),
        der = upd("MCEwCQYFKw4DAhoFAAQU");

    return {
        sha1: msrcryptoSha("SHA-1", der, h, k, 64, hashBlock, 160)
    };

})();

if (typeof operations !== "undefined") {

    msrcryptoSha1.hash = function ( p) {

        if (p.operationSubType === "process") {
            msrcryptoSha1.sha1.process(p.buffer);
            return;
        }

        if (p.operationSubType === "finish") {
            return msrcryptoSha1.sha1.finish();
        }

        return msrcryptoSha1.sha1.computeHash(p.buffer);

    };

    operations.register("digest", "sha-1", msrcryptoSha1.hash);

}

msrcryptoHashFunctions["sha-1"] = msrcryptoSha1.sha1;

var msrcryptoSha256 = (function () {

    var utils = msrcryptoUtilities;

    function hashBlock(message, blockIndex, hv, k, w) {
        /// <summary>
        /// Block function for hashing algorithm to use.
        /// </summary>
        /// <param name="message" type="Array">Block data to hash</param>
        /// <param name="blockIndex" type="Number">The block of the data to hash</param>
        /// <param name="hv" type="Array">Initial hash values</param>
        /// <param name="k" type="Array">K constants</param>
        /// <param name="w" type="Array">Buffer for w values</param>
        /// <returns type="Array">Updated initial hash values</returns>

        var t, i, temp, x0, x1, blockSize = 64, mask = 0xFFFFFFFF;

        var ra = hv[0],
            rb = hv[1],
            rc = hv[2],
            rd = hv[3],
            re = hv[4],
            rf = hv[5],
            rg = hv[6],
            rh = hv[7];

        // 0 ≤ t ≤ 15
        for (i = 0; i < 16; i++) {
            w[i] = utils.bytesToInt32(message, blockIndex * blockSize + i * 4);
        }

        // 16 ≤ t ≤ 63
        for (t = 16; t < 64; t++) {

            x0 = w[t - 15];
            x1 = w[t - 2];

            w[t] = (((x1 >>> 17) | (x1 << 15)) ^ ((x1 >>> 19) | (x1 << 13)) ^ (x1 >>> 10))
                    + w[t - 7]
                    + (((x0 >>> 7) | (x0 << 25)) ^ ((x0 >>> 18) | (x0 << 14)) ^ (x0 >>> 3))
                    + w[t - 16];

            w[t] = w[t] & mask;
        }

        for (i = 0; i < 64; i++) {

            temp = rh +
                    ((re >>> 6 | re << 26) ^ (re >>> 11 | re << 21) ^ (re >>> 25 | re << 7)) +
                    ((re & rf) ^ ((~re) & rg)) +
                    k[i] + w[i];

            rd += temp;

            temp += ((ra >>> 2 | ra << 30) ^ (ra >>> 13 | ra << 19) ^ (ra >>> 22 | ra << 10)) +
                    ((ra & (rb ^ rc)) ^ (rb & rc));

            rh = rg; // 'h' = g
            rg = rf; // 'g' = f
            rf = re; // 'f' = e
            re = rd; // 'e' = d
            rd = rc; // 'd' = c
            rc = rb; // 'c' = b
            rb = ra; // 'b' = a
            ra = temp; // 'a' = temp

        }

        // Update the hash values
        hv[0] += ra & mask;
        hv[1] += rb & mask;
        hv[2] += rc & mask;
        hv[3] += rd & mask;
        hv[4] += re & mask;
        hv[5] += rf & mask;
        hv[6] += rg & mask;
        hv[7] += rh & mask;

        return hv;
    }

    var k256, h224, h256, der224, der256, upd = utils.unpackData;

    h224 = upd("wQWe2DZ81QcwcN0X9w5ZOf/ACzFoWBURZPmPp776T6Q", 4, 1);

    h256 = upd("agnmZ7tnroU8bvNypU/1OlEOUn+bBWiMH4PZq1vgzRk", 4, 1);

    k256 = upd("QoovmHE3RJG1wPvP6bXbpTlWwltZ8RHxkj+CpKscXtXYB6qYEoNbASQxhb5VDH3Dcr5ddIDesf6b3AanwZvxdOSbacHvvkeGD8GdxiQMocwt6SxvSnSEqlywqdx2+YjamD5RUqgxxm2wAyfIv1l/x8bgC/PVp5FHBspjURQpKWcntwqFLhshOE0sbfxTOA0TZQpzVHZqCruBwskuknIshaK/6KGoGmZLwkuLcMdsUaPRkugZ1pkGJPQONYUQaqBwGaTBFh43bAgnSHdMNLC8tTkcDLNO2KpKW5zKT2gub/N0j4LueKVjb4TIeBSMxwIIkL7/+qRQbOu++aP3xnF48g", 4, 1);

    // SHA-224 DER encoding
    // 0x30, 0x2d, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x04, 0x05, 0x00, 0x04, 0x1C
    der224 = upd("MC0wDQYJYIZIAWUDBAIEBQAEHA");

    // SHA-256 DER encoding
    // 0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0x04, 0x20
    der256 = upd("MDEwDQYJYIZIAWUDBAIBBQAEIA");

    return {
        sha224: msrcryptoSha("SHA-224", der224, h224, k256, 64, hashBlock, 224),
        sha256: msrcryptoSha("SHA-256", der256, h256, k256, 64, hashBlock, 256)
    };
})();

if (typeof operations !== "undefined") {

    msrcryptoSha256.hash256 = function ( p) {

        if (p.operationSubType === "process") {
            msrcryptoSha256.sha256.process(p.buffer);
            return;
        }

        if (p.operationSubType === "finish") {
            return msrcryptoSha256.sha256.finish();
        }

        return msrcryptoSha256.sha256.computeHash(p.buffer);

    };

    msrcryptoSha256.hash224 = function ( p) {

        if (p.operationSubType === "process") {
            msrcryptoSha256.sha224.process(p.buffer);
            return;
        }

        if (p.operationSubType === "finish") {
            return msrcryptoSha256.sha224.finish();
        }

        return msrcryptoSha256.sha224.computeHash(p.buffer);

    };

    operations.register("digest", "sha-224", msrcryptoSha256.hash224);
    operations.register("digest", "sha-256", msrcryptoSha256.hash256);
}

msrcryptoHashFunctions["sha-224"] = msrcryptoSha256.sha224;
msrcryptoHashFunctions["sha-256"] = msrcryptoSha256.sha256;

var msrcryptoSha512 = (function () {

    var utils = msrcryptoUtilities;

    function add(x0, x1, y0, y1, resultArray) {

        // The sum here may result in a number larger than 32-bits.
        // Or-ing with zero forces to a 32-bit signed internal state
        // and a truncation to a 32-bit number;
        var lowSum = (x1 + y1) | 0;

        // If lowSum is less than either parameter (x1 or y1), we know we overflowed
        //   and a carry will need to be added to the high order bits.
        // The 32-bit integer is signed. So large numbers can flip to negative values.
        // The zero-shift pulls the number back out of the 32-bit state so we know
        //   we're comparing positive values.
        var carry = (lowSum >>> 0 < y1 >>> 0);

        resultArray[0] = (x0 + y0 + carry) | 0;
        resultArray[1] = lowSum;

        return;
    }

    function hashBlock(message, blockIndex, hv, k, w) {
        /// <summary>
        /// Block function for hashing algorithm to use.
        /// </summary>
        /// <param name="message" type="Array">Block data to hash</param>
        /// <param name="blockIndex" type="Number">The block of the data to hash</param>
        /// <param name="hv" type="Array">Initial hash values</param>
        /// <param name="k" type="Array">K constants</param>
        /// <param name="w" type="Array">Buffer for w values</param>
        /// <returns type="Array">Updated initial hash values</returns>

        var t, i, blockBytes = 128,
            tah, tal, tbh, tbl, xh, xl,
            tc = [], td = [], te = [], index;

        var ah = hv[0], al = hv[1],
            bh = hv[2], bl = hv[3],
            ch = hv[4], cl = hv[5],
            dh = hv[6], dl = hv[7],
            eh = hv[8], el = hv[9],
            fh = hv[10], fl = hv[11],
            gh = hv[12], gl = hv[13],
            hh = hv[14], hl = hv[15];

        for (t = 0; t < 32; t++) {
            index = blockIndex * blockBytes + t * 4;
            w[t] = message.slice(index, index + 4);
            w[t] = (w[t][0] << 24) | (w[t][1] << 16) | (w[t][2] << 8) | w[t][3];
        }

        // 16 ≤ t ≤ 80
        for (t = 32; t < 160; t += 2) {

            xh = w[t - 30];
            xl = w[t - 29];

            tah = (xh >>> 1 | xl << 31) ^ (xh >>> 8 | xl << 24) ^ (xh >>> 7);
            tal = (xl >>> 1 | xh << 31) ^ (xl >>> 8 | xh << 24) ^ (xl >>> 7 | xh << 25);

            xh = w[t - 4];
            xl = w[t - 3];

            tbh = (xh >>> 19 | xl << 13) ^ (xl >>> 29 | xh << 3) ^ (xh >>> 6);
            tbl = (xl >>> 19 | xh << 13) ^ (xh >>> 29 | xl << 3) ^ (xl >>> 6 | xh << 26);

            add(tbh, tbl, w[t - 14], w[t - 13], tc);

            add(tah, tal, tc[0], tc[1], tc);

            add(w[t - 32], w[t - 31], tc[0], tc[1], tc);

            w[t] = tc[0]; w[t + 1] = tc[1];
        }

        for (i = 0; i < 160; i += 2) {

            // S1 =======================================================================
            tah = (eh >>> 14 | el << 18) ^ (eh >>> 18 | el << 14) ^ (el >>> 9 | eh << 23);
            tal = (el >>> 14 | eh << 18) ^ (el >>> 18 | eh << 14) ^ (eh >>> 9 | el << 23);

            // Ch
            tbh = (eh & fh) ^ (gh & ~eh);
            tbl = (el & fl) ^ (gl & ~el);

            // C = h + S1
            add(hh, hl, tah, tal, tc);

            // D = ch + kConstants-i
            add(tbh, tbl, k[i], k[i + 1], td);

            // E = w[i] + C
            add(tc[0], tc[1], w[i], w[i + 1], te);

            // E = E + D  TEMP
            add(td[0], td[1], te[0], te[1], te);

            // D = C + E
            add(te[0], te[1], dh, dl, tc);
            dh = tc[0]; dl = tc[1];

            // S0
            tal = (al >>> 28 | ah << 4) ^ (ah >>> 2 | al << 30) ^ (ah >>> 7 | al << 25);
            tah = (ah >>> 28 | al << 4) ^ (al >>> 2 | ah << 30) ^ (al >>> 7 | ah << 25);

            tbl = (al & (bl ^ cl)) ^ (bl & cl);
            tbh = (ah & (bh ^ ch)) ^ (bh & ch);

            // S0 + maj
            add(te[0], te[1], tah, tal, tc);
            tah = tc[0]; tal = tc[1];

            // 'temp' = temp + (S0 + maj)
            add(tbh, tbl, tah, tal, tc);
            tah = tc[0]; tal = tc[1];

            hh = gh;
            hl = gl; // 'h' = g
            gh = fh;
            gl = fl; // 'g' = f
            fh = eh;
            fl = el; // 'f' = e
            eh = dh;
            el = dl; // 'e' = d
            dh = ch;
            dl = cl; // 'd' = c
            ch = bh;
            cl = bl; // 'c' = b
            bh = ah;
            bl = al; // 'b' = a
            ah = tah;
            al = tal; // 'a' = temp
        }

        // This is how you would add without calling add()
        // hv[1] = ((hv[1] + al) | 0) >>> 0;
        // hv[0] = hv[0] + ah + (hv[1] < al >>> 0) | 0;

        add(hv[0], hv[1], ah, al, tc);
        hv[0] = tc[0]; hv[1] = tc[1];

        add(hv[2], hv[3], bh, bl, tc);
        hv[2] = tc[0]; hv[3] = tc[1];

        add(hv[4], hv[5], ch, cl, tc);
        hv[4] = tc[0]; hv[5] = tc[1];

        add(hv[6], hv[7], dh, dl, tc);
        hv[6] = tc[0]; hv[7] = tc[1];

        add(hv[8], hv[9], eh, el, tc);
        hv[8] = tc[0]; hv[9] = tc[1];

        add(hv[10], hv[11], fh, fl, tc);
        hv[10] = tc[0]; hv[11] = tc[1];

        add(hv[12], hv[13], gh, gl, tc);
        hv[12] = tc[0]; hv[13] = tc[1];

        add(hv[14], hv[15], hh, hl, tc);
        hv[14] = tc[0]; hv[15] = tc[1];

        return hv;
    }

    var h384, h512, k512,
        der384, der512, der512_224, der512_256,
        upd = utils.unpackData;

    h384 = upd("y7udXcEFnthimikqNnzVB5FZAVowcN0XFS/s2PcOWTlnMyZn/8ALMY60SodoWBUR2wwuDWT5j6dHtUgdvvpPpA==", 4, 1);

    h512 = upd("agnmZ/O8yQi7Z66FhMqnOzxu83L+lPgrpU/1Ol8dNvFRDlJ/reaC0ZsFaIwrPmwfH4PZq/tBvWtb4M0ZE34heQ", 4, 1);

    k512 = upd(
        "QoovmNcoriJxN0SRI+9lzbXA+8/sTTsv6bXbpYGJ27w5VsJb80i1OFnxEfG2BdAZkj+CpK8ZT5urHF7" +
        "V2m2BGNgHqpijAwJCEoNbAUVwb74kMYW+TuSyjFUMfcPV/7Ticr5ddPJ7iW+A3rH+OxaWsZvcBqclxx" +
        "I1wZvxdM9pJpTkm2nBnvFK0u++R4Y4TyXjD8GdxouM1bUkDKHMd6ycZS3pLG9ZKwJ1SnSEqm6m5INcs" +
        "KncvUH71Hb5iNqDEVO1mD5RUu5m36uoMcZtLbQyELADJ8iY+yE/v1l/x77vDuTG4AvzPaiPwtWnkUeT" +
        "CqclBspjUeADgm8UKSlnCg5ucCe3CoVG0i/8LhshOFwmySZNLG38WsQq7VM4DROdlbPfZQpzVIuvY95" +
        "2agq7PHeyqIHCyS5H7a7mknIshRSCNTuiv+ihTPEDZKgaZku8QjABwkuLcND4l5HHbFGjBlS+MNGS6B" +
        "nW71IY1pkGJFVlqRD0DjWFV3EgKhBqoHAyu9G4GaTBFrjS0MgeN2wIUUGrUydId0zfjuuZNLC8teGbS" +
        "Kg5HAyzxclaY07YqkrjQYrLW5zKT3dj43NoLm/z1rK4o3SPgu5d77L8eKVjb0MXL2CEyHgUofCrcozH" +
        "AggaZDnskL7/+iNjHiikUGzr3oK96b75o/eyxnkVxnF48uNyUyvKJz7O6iZhnNGGuMchwMIH6tp91s3" +
        "g6x71fU9/7m7ReAbwZ6pyF2+6CmN9xaLImKYRP5gEvvkNrhtxCzUTHEcbKNt39SMEfYQyyqt7QMckkz" +
        "yevgoVyb68Qx1nxJwQDUxMxdS+yz5Ctll/KZz8ZX4qX8tvqzrW+uxsRBmMSkdYFw==", 4, 1);

    // DER encoding
    der384 = upd("MEEwDQYJYIZIAWUDBAICBQAEMA");
    der512 = upd("MFEwDQYJYIZIAWUDBAIDBQAEQA");
    der512_224 = upd("MC0wDQYJYIZIAWUDBAIFBQAEHA");
    der512_256 = upd("MDEwDQYJYIZIAWUDBAIGBQAEIA");

    return {
        sha384: msrcryptoSha("SHA-384", der384, h384, k512, 128, hashBlock, 384),
        sha512: msrcryptoSha("SHA-512", der512, h512, k512, 128, hashBlock, 512),
        sha512_224: msrcryptoSha("SHA-512.224", der512_224, h512, k512, 128, hashBlock, 224),
        sha512_256: msrcryptoSha("SHA-512.256", der512_256, h512, k512, 128, hashBlock, 256)
    };

})();

if (typeof operations !== "undefined") {

    msrcryptoSha512.hash384 = function ( p) {

        if (p.operationSubType === "process") {
            msrcryptoSha512.sha384.process(p.buffer);
            return;
        }

        if (p.operationSubType === "finish") {
            return msrcryptoSha512.sha384.finish();
        }

        return msrcryptoSha512.sha384.computeHash(p.buffer);

    };

    msrcryptoSha512.hash512 = function ( p) {

        if (p.operationSubType === "process") {
            msrcryptoSha512.sha512.process(p.buffer);
            return;
        }

        if (p.operationSubType === "finish") {
            return msrcryptoSha512.sha512.finish();
        }

        return msrcryptoSha512.sha512.computeHash(p.buffer);

    };

    operations.register("digest", "sha-384", msrcryptoSha512.hash384);
    operations.register("digest", "sha-512", msrcryptoSha512.hash512);
}

msrcryptoHashFunctions["sha-384"] = msrcryptoSha512.sha384;
msrcryptoHashFunctions["sha-512"] = msrcryptoSha512.sha512;

var msrcryptoHmac = (function () {

    var sha256,
        sha512,
        sha1;

    if (typeof msrcryptoSha256 !== "undefined") {
        sha256 = msrcryptoSha256;
    }

    if (typeof msrcryptoSha512 !== "undefined") {
        sha512 = msrcryptoSha512;
    }

    if (typeof msrcryptoSha1 !== "undefined") {
        sha1 = msrcryptoSha1;
    }

    var hashFunction;
    var blockSize;
    var keyBytes;

    var ipad;
    var opad;

    function xorArrays(array1, array2) {
        var newArray = new Array(array1);
        for (var i = 0 ; i < array1.length; i++) {
            newArray[i] = array1[i] ^ array2[i];
        }
        return newArray;
    }

    // Returns a new Array with zeroes padded on the end
    function padZeros(bytes, paddedLength) {
        var paddedArray = bytes.slice();
        for (var i = bytes.length ; i < paddedLength; i++) {
            paddedArray.push(0);
        }
        return paddedArray;
    }

    function padKey() {

        if (keyBytes.length === blockSize) {
            return keyBytes;
        }

        if (keyBytes.length > blockSize) {
            return padZeros(hashFunction.computeHash(keyBytes), blockSize);
        }

        // If keyBytes.length < blockSize
        return padZeros(keyBytes, blockSize);

    }

    var paddedKey = null;
    var keyXorOpad;

    function processHmac(messageBytes) {

        var keyXorIpad;
        var k0IpadText;

        // If this is the first process call, do some initial computations
        if (!paddedKey) {
            ipad = new Array(blockSize);
            opad = new Array(blockSize);
            for (var i = 0; i < blockSize; i++) { ipad[i] = 0x36; opad[i] = 0x5c; }

            paddedKey = padKey();
            keyXorIpad = xorArrays(paddedKey, ipad);
            keyXorOpad = xorArrays(paddedKey, opad);
            k0IpadText = keyXorIpad.concat(messageBytes);
            hashFunction.process(k0IpadText);

            // Subsequent process calls just add to the hash
        } else {

            hashFunction.process(messageBytes);
        }

        return;
    }

    function finishHmac() {

        var hashK0IpadText = hashFunction.finish();

        var k0IpadK0OpadText = keyXorOpad.concat(hashK0IpadText);

        return hashFunction.computeHash(k0IpadK0OpadText);
    }

    function clearState() {
        keyBytes = null;
        hashFunction = null;
        paddedKey = null;
    }

    function selectHashAlgorithm(hashAlgorithmName) {

        switch (hashAlgorithmName.toLowerCase()) {

            case "sha-1":
                if (sha1 === undefined) {
                    throw new Error("Sha1 object not found");
                }
                hashFunction = sha1.sha1;
                blockSize = 64;
                break;

            case "sha-224":
                hashFunction = sha256.sha224;
                blockSize = 64;
                break;

            case "sha-256":
                hashFunction = sha256.sha256;
                blockSize = 64;
                break;

            case "sha-384":
                if (sha512 === undefined) {
                    throw new Error("Sha512 object not found");
                }
                hashFunction = sha512.sha384;
                blockSize = 128;
                break;

            case "sha-512":
                if (sha512 === undefined) {
                    throw new Error("Sha512 object not found");
                }
                hashFunction = sha512.sha512;
                blockSize = 128;
                break;

            default:
                throw new Error("unsupported hash alorithm (sha-224, sha-256, sha-384, sha-512)");
        }

    }

    return {

        computeHmac: function (dataBytes, key, hashAlgorithm) {
            /// <summary>Computes the HMAC</summary>
            /// <param name="dataBytes" type="Array">Data to MAC</param>
            /// <param name="key" type="Array">Array of bytes for key</param>
            /// <param name="hashAlgorithm" type="String">sha-224, sha-256, sha-384, sha-512 (default sha-256)</param>
            /// <returns type="Array">Returns an array of bytes as the HMAC</returns>

            keyBytes = key;

            selectHashAlgorithm(hashAlgorithm);

            processHmac(dataBytes);

            var result = finishHmac();

            clearState();

            return result;
        },

        process: function (dataBytes, key, hashAlgorithm) {
            /// <summary>Computes a partial HMAC to be followed by subsequent process calls or finish()</summary>
            /// <param name="dataBytes" type="Array">Data to MAC</param>
            /// <param name="key" type="Array">Array of bytes for key</param>
            /// <param name="hashAlgorithm" type="String">sha-224, sha-256, sha-384, sha-512 (default sha-256)</param>

            if (!hashFunction) {
                keyBytes = key;
                selectHashAlgorithm(hashAlgorithm);
            }

            processHmac(dataBytes);
        },

        finish: function (key, hashAlgorithm) {
            /// <summary>Computes the final HMAC upon partial computations from previous process() calls.</summary>
            /// <param name="key" type="Array">Array of bytes for key</param>
            /// <param name="hashAlgorithm" type="String">sha-224, sha-256, sha-384, sha-512 (default sha-256)</param>
            /// <returns type="Array">Returns an array of bytes as the HMAC</returns>

            // Finish could be called before any processing. We'll return the hmac
            // of an empty buffer.
            if (!hashFunction) {
                keyBytes = key;
                selectHashAlgorithm(hashAlgorithm);
                processHmac([]);
            }

            var result = finishHmac();
            clearState();
            return result;
        }

    };
})();

if (typeof operations !== "undefined") {

    msrcryptoHmac.signHmac = function (p) {

        var hashName = p.algorithm.hash.name;

        if (p.operationSubType === "process") {
            msrcryptoHmac.process(p.buffer, p.keyData, hashName);
            return;
        }

        if (p.operationSubType === "finish") {
            return msrcryptoHmac.finish(p.keyData, hashName);
        }

        return msrcryptoHmac.computeHmac(p.buffer, p.keyData, hashName);
    };

    msrcryptoHmac.verifyHmac = function (p) {

        var hashName = p.algorithm.hash.name;

        if (p.operationSubType === "process") {
            msrcryptoHmac.process(p.buffer, p.keyData, hashName);
            return;
        }

        if (p.operationSubType === "finish") {
            return msrcryptoUtilities.arraysEqual(msrcryptoHmac.finish(p.keyData, hashName), p.signature);
        }

        return msrcryptoUtilities.arraysEqual(msrcryptoHmac.computeHmac(p.buffer, p.keyData, hashName), p.signature);
    };

    msrcryptoHmac.generateKey = function (p) {

        var keyLength = p.algorithm.length;

        var defaultKeyLengths = { "sha-256": 32, "sha-384": 48, "sha-512": 64 };

        if (!keyLength) {
            keyLength = defaultKeyLengths[p.algorithm.hash.name.toLowerCase()];
        }

        return {
            type: "keyGeneration",
            keyData: msrcryptoPseudoRandom.getBytes(keyLength),
            keyHandle: {
                algorithm: p.algorithm,
                extractable: p.extractable,
                keyUsage: null || p.keyUsage,
                type: "secret"
            }
        };
    };

    msrcryptoHmac.importKey = function (p) {

        var keyObject = msrcryptoJwk.jwkToKey(p.keyData, p.algorithm, ["k"]);

        keyObject.alg = keyObject.alg.replace("HS", "sha-");

        return {
            type: "keyImport",
            keyData: keyObject.k,
            keyHandle: {
                algorithm: { name: "hmac", hash: { name: keyObject.alg } },
                extractable: p.extractable || keyObject.extractable,
                keyUsage: null || p.keyUsage, // IE11 returns null here
                type: "secret"
            }
        };

    };

    msrcryptoHmac.exportKey = function (p) {

        var jsonKeyStringArray = msrcryptoJwk.keyToJwk(p.keyHandle, p.keyData);

        return { type: "keyExport", keyHandle: jsonKeyStringArray };
    };

    operations.register("importKey", "hmac", msrcryptoHmac.importKey);
    operations.register("exportKey", "hmac", msrcryptoHmac.exportKey);
    operations.register("generateKey", "hmac", msrcryptoHmac.generateKey);
    operations.register("sign", "hmac", msrcryptoHmac.signHmac);
    operations.register("verify", "hmac", msrcryptoHmac.verifyHmac);
}

var msrcryptoBlockCipher = (function()  {

    var aesConstants,
    multByTwo,
    multByThree,
    multBy14,
    multBy13,
    multBy11,
    multBy9,
    sBoxTable,
    invSBoxTable,
    rConTable;

    return {

        /// <summary>Advanced Encryption Standard implementation per FIPS 197.</summary>
        aes: function ( keyBytes) {

            // Set up the constants the first time we create an AES object only.
            if (!aesConstants) {
                aesConstants = msrcryptoUtilities.unpackData("AAIEBggKDA4QEhQWGBocHiAiJCYoKiwuMDI0Njg6PD5AQkRGSEpMTlBSVFZYWlxeYGJkZmhqbG5wcnR2eHp8foCChIaIioyOkJKUlpianJ6goqSmqKqsrrCytLa4ury+wMLExsjKzM7Q0tTW2Nrc3uDi5Obo6uzu8PL09vj6/P4bGR8dExEXFQsJDw0DAQcFOzk/PTMxNzUrKS8tIyEnJVtZX11TUVdVS0lPTUNBR0V7eX99c3F3dWtpb21jYWdlm5mfnZORl5WLiY+Ng4GHhbu5v72zsbe1q6mvraOhp6Xb2d/d09HX1cvJz83DwcfF+/n//fPx9/Xr6e/t4+Hn5QADBgUMDwoJGBseHRQXEhEwMzY1PD86OSgrLi0kJyIhYGNmZWxvaml4e359dHdycVBTVlVcX1pZSEtOTURHQkHAw8bFzM/Kydjb3t3U19LR8PP29fz/+vno6+7t5Ofi4aCjpqWsr6qpuLu+vbS3srGQk5aVnJ+amYiLjo2Eh4KBm5idnpeUkZKDgIWGj4yJiquora6npKGis7C1tr+8ubr7+P3+9/Tx8uPg5ebv7Onqy8jNzsfEwcLT0NXW39zZ2ltYXV5XVFFSQ0BFRk9MSUpraG1uZ2RhYnNwdXZ/fHl6Ozg9Pjc0MTIjICUmLywpKgsIDQ4HBAECExAVFh8cGRoADhwSODYkKnB+bGJIRlRa4O788tjWxMqQnoyCqKa0utvVx8nj7f/xq6W3uZOdj4E7NScpAw0fEUtFV1lzfW9hraOxv5WbiYfd08HP5ev5901DUV91e2lnPTMhLwULGRd2eGpkTkBSXAYIGhQ+MCIslpiKhK6gsrzm6Pr03tDCzEFPXVN5d2VrMT8tIwkHFRuhr72zmZeFi9HfzcPp5/X7mpSGiKKsvrDq5Pb40tzOwHp0ZmhCTF5QCgQWGDI8LiDs4vD+1NrIxpySgI6kqri2DAIQHjQ6KCZ8cmBuREpYVjc5KyUPARMdR0lbVX9xY23X2cvF7+Hz/aepu7WfkYONAA0aFzQ5LiNoZXJ/XFFGS9Ddysfk6f7zuLWir4yBlpu7tqGsj4KVmNPeycTn6v3wa2ZxfF9SRUgDDhkUNzotIG1gd3pZVENOBQgfEjE8Kya9sKeqiYSTntXYz8Lh7Pv21tvMweLv+PW+s6SpioeQnQYLHBEyPyglbmN0eVpXQE3a18DN7uP0+bK/qKWGi5yRCgcQHT4zJClib3h1VltMQWFse3ZVWE9CCQQTHj0wJyqxvKumhYifktnUw87t4Pf6t7qtoIOOmZTf0sXI6+bx/GdqfXBTXklEDwIVGDs2ISwMARYbODUiL2RpfnNQXUpH3NHGy+jl8v+0ua6jgI2alwALFh0sJzoxWFNORXR/Ymmwu6atnJeKgejj/vXEz9LZe3BtZldcQUojKDU+DwQZEsvA3dbn7PH6k5iFjr+0qaL2/eDr2tHMx66luLOCiZSfRk1QW2phfHceFQgDMjkkL42Gm5Chqre81d7DyPny7+Q9NisgERoHDGVuc3hJQl9U9/zh6tvQzcavpLmyg4iVnkdMUVprYH12HxQJAjM4JS6Mh5qRoKu2vdTfwsn48+7lPDcqIRAbBg1kb3J5SENeVQEKFxwtJjswWVJPRHV+Y2ixuqesnZaLgOni//TFztPYenFsZ1ZdQEsiKTQ/DgUYE8rB3Nfm7fD7kpmEj761qKMACRIbJC02P0hBWlNsZX53kJmCi7S9pq/Y0crD/PXu5zsyKSAfFg0Ec3phaFdeRUyrormwj4adlOPq8fjHztXcdn9kbVJbQEk+NywlGhMIAebv9P3Cy9DZrqe8tYqDmJFNRF9WaWB7cgUMFx4hKDM63dTPxvnw6+KVnIeOsbijquzl/vfIwdrTpK22v4CJkpt8dW5nWFFKQzQ9Ji8QGQIL197FzPP64eiflo2Eu7KpoEdOVVxjanF4DwYdFCsiOTCak4iBvrespdLbwMn2/+TtCgMYES4nPDVCS1BZZm90faGos7qFjJee6eD78s3E39YxOCMqFRwHDnlwa2JdVE9GY3x3e/Jrb8UwAWcr/terdsqCyX36WUfwrdSir5ykcsC3/ZMmNj/3zDSl5fFx2DEVBMcjwxiWBZoHEoDi6yeydQmDLBobblqgUjvWsynjL4RT0QDtIPyxW2rLvjlKTFjP0O+q+0NNM4VF+QJ/UDyfqFGjQI+SnTj1vLbaIRD/89LNDBPsX5dEF8Snfj1kXRlzYIFP3CIqkIhG7rgU3l4L2+AyOgpJBiRcwtOsYpGV5HnnyDdtjdVOqWxW9Opleq4IunglLhymtMbo3XQfS72LinA+tWZIA/YOYTVXuYbBHZ7h+JgRadmOlJseh+nOVSjfjKGJDb/mQmhBmS0PsFS7FlIJatUwNqU4v0CjnoHz1/t84zmCmy//hzSOQ0TE3unLVHuUMqbCIz3uTJULQvrDTgguoWYo2SSydluiSW2L0SVy+PZkhmiYFtSkXMxdZbaSbHBIUP3tudpeFUZXp42dhJDYqwCMvNMK9+RYBbizRQbQLB6Pyj8PAsGvvQMBE4prOpERQU9n3OqX8s/O8LTmc5asdCLnrTWF4vk36Bx1325H8RpxHSnFiW+3Yg6qGL4b/FY+S8bSeSCa28D+eM1a9B/dqDOIB8cxsRIQWSeA7F9gUX+pGbVKDS3lep+TyZzvoOA7Ta4q9bDI67s8g1OZYRcrBH66d9Ym4WkUY1UhDH2NAQIECBAgQIAbNmzYq02aL168Y8aXNWrUs33678WROXLk071hwp8lSpQzZsyDHTp06MuNAQIECBAgQIAbNmzYq02aL168Y8aXNWrUs33678WROXLk071hwp8lSpQzZsyDHTp06MuNAQIECBAgQIAbNmzYq02aL168Y8aXNWrUs33678WROXLk071hwp8lSpQzZsyDHTp06MuNAQIECBAgQIAbNmzYq02aL168Y8aXNWrUs33678WROXLk071hwp8lSpQzZsyDHTp06MuNAQIECBAgQIAbNmzYq02aL168Y8aXNWrUs33678WROXLk071hwp8lSpQzZsyDHTp06MuN", 256, false);
                multByTwo = aesConstants[0];
                multByThree = aesConstants[1];
                multBy14 = aesConstants[2];
                multBy13 = aesConstants[3];
                multBy11 = aesConstants[4];
                multBy9 = aesConstants[5];
                sBoxTable = aesConstants[6];
                invSBoxTable = aesConstants[7];
                rConTable = aesConstants[8];
            }

            var blockSize = 128,
                keyLength,
                nK,
                nB = 4,
                nR,
                key;

            switch (keyBytes.length * 8) {

                case 128:
                    keyLength = 128;
                    nK = 4;
                    nR = 10;
                    break;

                case 192:
                    keyLength = 192;
                    nK = 6;
                    nR = 12;
                    break;

                case 256:
                    keyLength = 256;
                    nK = 8;
                    nR = 14;
                    break;

                default:
                    throw new Error("Unsupported keyLength");
            }

            var shiftRows = function (a) {
                var tmp = a[1]; a[1] = a[5]; a[5] = a[9]; a[9] = a[13]; a[13] = tmp;
                tmp = a[2]; a[2] = a[10]; a[10] = tmp;
                tmp = a[6]; a[6] = a[14]; a[14] = tmp;
                tmp = a[15]; a[15] = a[11]; a[11] = a[7]; a[7] = a[3]; a[3] = tmp;
            };

            var invShiftRows = function (a) {
                var tmp = a[13]; a[13] = a[9]; a[9] = a[5]; a[5] = a[1]; a[1] = tmp;
                tmp = a[10]; a[10] = a[2]; a[2] = tmp;
                tmp = a[14]; a[14] = a[6]; a[6] = tmp;
                tmp = a[3]; a[3] = a[7]; a[7] = a[11]; a[11] = a[15]; a[15] = tmp;
            };

            var mixColumns = function (state) {
                /// <summary>Operates on the state column by column, performing a multiplication by x^4 + 1 in GF(2^8)</summary>
                /// <param name="state" type="Array"> the current state (length 16)</param>
                /// <returns type="Array">The mixed state</returns>
                var a = state[0], b = state[1], c = state[2], d = state[3],
                    e = state[4], f = state[5], g = state[6], h = state[7],
                    i = state[8], j = state[9], k = state[10], l = state[11],
                    m = state[12], n = state[13], o = state[14], p = state[15];

                state[0] = multByTwo[a] ^ multByThree[b] ^ c ^ d;
                state[1] = a ^ multByTwo[b] ^ multByThree[c] ^ d;
                state[2] = a ^ b ^ multByTwo[c] ^ multByThree[d];
                state[3] = multByThree[a] ^ b ^ c ^ multByTwo[d];
                state[4] = multByTwo[e] ^ multByThree[f] ^ g ^ h;
                state[5] = e ^ multByTwo[f] ^ multByThree[g] ^ h;
                state[6] = e ^ f ^ multByTwo[g] ^ multByThree[h];
                state[7] = multByThree[e] ^ f ^ g ^ multByTwo[h];
                state[8] = multByTwo[i] ^ multByThree[j] ^ k ^ l;
                state[9] = i ^ multByTwo[j] ^ multByThree[k] ^ l;
                state[10] = i ^ j ^ multByTwo[k] ^ multByThree[l];
                state[11] = multByThree[i] ^ j ^ k ^ multByTwo[l];
                state[12] = multByTwo[m] ^ multByThree[n] ^ o ^ p;
                state[13] = m ^ multByTwo[n] ^ multByThree[o] ^ p;
                state[14] = m ^ n ^ multByTwo[o] ^ multByThree[p];
                state[15] = multByThree[m] ^ n ^ o ^ multByTwo[p];
            };

            var invMixColumns = function (state) {
                /// <summary>Operates on the state column by column, performing a multiplication by x^4 + 1 in GF(2^8)</summary>
                /// <param name="state" type="Array"> the current state (length 16)</param>
                /// <returns type="Array">The mixed state</returns>
                var a = state[0], b = state[1], c = state[2], d = state[3],
                    e = state[4], f = state[5], g = state[6], h = state[7],
                    i = state[8], j = state[9], k = state[10], l = state[11],
                    m = state[12], n = state[13], o = state[14], p = state[15];

                state[0] = multBy14[a] ^ multBy11[b] ^ multBy13[c] ^ multBy9[d];
                state[1] = multBy9[a] ^ multBy14[b] ^ multBy11[c] ^ multBy13[d];
                state[2] = multBy13[a] ^ multBy9[b] ^ multBy14[c] ^ multBy11[d];
                state[3] = multBy11[a] ^ multBy13[b] ^ multBy9[c] ^ multBy14[d];
                state[4] = multBy14[e] ^ multBy11[f] ^ multBy13[g] ^ multBy9[h];
                state[5] = multBy9[e] ^ multBy14[f] ^ multBy11[g] ^ multBy13[h];
                state[6] = multBy13[e] ^ multBy9[f] ^ multBy14[g] ^ multBy11[h];
                state[7] = multBy11[e] ^ multBy13[f] ^ multBy9[g] ^ multBy14[h];
                state[8] = multBy14[i] ^ multBy11[j] ^ multBy13[k] ^ multBy9[l];
                state[9] = multBy9[i] ^ multBy14[j] ^ multBy11[k] ^ multBy13[l];
                state[10] = multBy13[i] ^ multBy9[j] ^ multBy14[k] ^ multBy11[l];
                state[11] = multBy11[i] ^ multBy13[j] ^ multBy9[k] ^ multBy14[l];
                state[12] = multBy14[m] ^ multBy11[n] ^ multBy13[o] ^ multBy9[p];
                state[13] = multBy9[m] ^ multBy14[n] ^ multBy11[o] ^ multBy13[p];
                state[14] = multBy13[m] ^ multBy9[n] ^ multBy14[o] ^ multBy11[p];
                state[15] = multBy11[m] ^ multBy13[n] ^ multBy9[o] ^ multBy14[p];
            };

            var xorWord = function (a, b) {
                return [a[0] ^ b[0], a[1] ^ b[1], a[2] ^ b[2], a[3] ^ b[3]];
            };

            var addRoundKey = function ( state, keySchedule, offset) {
                for (var i = 0 ; i < state.length ; i += 1) {
                    state[i] ^= keySchedule[i + offset];
                }
            };

            var rotWord = function ( word) {
                var a = word[0];
                word[0] = word[1]; word[1] = word[2]; word[2] = word[3]; word[3] = a;
            };

            var subWord = function ( word) {
                for (var i = 0 ; i < word.length ; i += 1) {
                    word[i] = sBoxTable[word[i]];
                }
            };

            var invSubWord = function ( word) {
                for (var i = 0 ; i < word.length ; i += 1) {
                    word[i] = invSBoxTable[word[i]];
                }
            };

            var getWord = function (tab, i) {
                return [tab[4 * i], tab[4 * i + 1], tab[4 * i + 2], tab[4 * i + 3]];
            };

            var setWord = function ( left, right, indexL, indexR) {
                left[4 * indexL] = right[4 * indexR];
                left[4 * indexL + 1] = right[4 * indexR + 1];
                left[4 * indexL + 2] = right[4 * indexR + 2];
                left[4 * indexL + 3] = right[4 * indexR + 3];
            };

            var expandKey = function (key) {
                var temp, res = [], i = 0;
                while (i < 4 * nK) {
                    res.push(key[i++]);
                }

                i = nK;
                while (i < nB * (nR + 1)) {
                    temp = getWord(res, i - 1);
                    if (i % nK === 0) {
                        var index = i / nK;
                        var rcon = [rConTable[index], 0, 0, 0];
                        rotWord(temp);
                        subWord(temp);
                        temp = xorWord(temp, rcon);
                    } else if (nK > 6 && i % nK === 4) {
                        subWord(temp);
                    }
                    var newWord = xorWord(getWord(res, i - nK), temp);
                    setWord(res, newWord, i, 0);
                    i += 1;
                }
                return res;
            };

            key = expandKey(keyBytes);

            return {

                encrypt: function (dataBytes) {
                    var state = dataBytes,
                        round;

                    addRoundKey(state, key, 0);
                    for (round = 1 ; round <= nR - 1 ; round += 1) {
                        subWord(state);
                        shiftRows(state);
                        mixColumns(state);
                        addRoundKey(state, key, 4 * round * nB);
                    }
                    subWord(state);
                    shiftRows(state);
                    addRoundKey(state, key, 4 * nR * nB);

                    return state;
                },

                decrypt: function (dataBytes) {
                    var state = dataBytes,
                        round;

                    addRoundKey(state, key, 4 * nR * nB);
                    for (round = nR - 1 ; round >= 1 ; round -= 1) {
                        invShiftRows(state);
                        invSubWord(state);
                        addRoundKey(state, key, 4 * round * nB);
                        invMixColumns(state);
                    }
                    invShiftRows(state);
                    invSubWord(state);
                    addRoundKey(state, key, 0);

                    return state;
                },

                clear: function () {
                    // Reset the state
                },

                keyLength: keyLength,

                blockSize: blockSize

            };
        }

    };

})();

var msrcryptoPadding = msrcryptoPadding || {};

msrcryptoPadding.pkcsv7 = function (blockSize) {

    function pad(messageBlocks) {
        /// <signature>
        ///     <summary>Apply PKCS7 padding to message</summary>
        ///     <param name="messageBlocks" type="Array">An array of blocks to pad</param> <
        /// </signature>

        var lastIndex = messageBlocks.length - 1 >= 0 ? messageBlocks.length - 1 : 0,
            lastBlock = messageBlocks[lastIndex],
            lastBlockLength = lastBlock.length,
            createNewBlock = (lastBlockLength === blockSize);

        if (createNewBlock) {
            var newBlock = [], i;
            for (i = 0 ; i < blockSize; i += 1) {
                newBlock.push(blockSize);
            }
            messageBlocks.push(newBlock);
        } else {
            var byteToAdd = (blockSize - lastBlockLength) & 0xff;
            while (lastBlock.length !== blockSize) {
                lastBlock.push(byteToAdd);
            }
        }

    }

    function unpad(messageBytes) {
        /// <signature>
        ///     <summary>Remove PKCS7 padding from the message</summary>
        ///     <param name="messageBytes" type="Array"></param>
        ///     <returns type="Boolean">True for legal padding. False if not.</returns>
        /// </signature>

        var verified = true;

        // Verify the cipher text is an increment of block length
        if (messageBytes.length % blockSize !== 0) {
            verified = false;
        }

        // Get the last block
        var lastBlock = messageBytes.slice(-blockSize);

        // Get value of the last element in the block
        // This will be the number of padding bytes on the end if the
        // message was decrypted correctly.
        var padLen = lastBlock[lastBlock.length - 1];

        for (var i = 0; i < blockSize; i++) {
            var isPaddingElement = (blockSize - i <= padLen);
            var isCorrectValue = (lastBlock[i] === padLen);
            verified = (isPaddingElement ? isCorrectValue : true) && verified;
        }

        var trimLen = verified ? padLen : 0;

        messageBytes.length -= trimLen;

        return verified;
    }

    return {
        pad: pad,
        unpad: unpad
    };

};

var msrcryptoCbc = function (blockCipher) {

    var blockSize = blockCipher.blockSize / 8;

    var paddingScheme = msrcryptoPadding.pkcsv7(blockSize);

    // Merges an array of block arrays into a single byte array
    var mergeBlocks = function ( tab) {
        var res = [], i, j;
        for (i = 0 ; i < tab.length; i += 1) {
            var block = tab[i];
            for (j = 0 ; j < block.length; j += 1) {
                res.push(block[j]);
            }
        }
        return res;
    };

    // Breaks an array of bytes into an array of block size arrays of bytes
    function getBlocks(dataBytes) {

        var blocks = [];

        // Append incoming bytes to the end of the existing buffered bytes
        mBuffer = mBuffer.concat(dataBytes);

        var blockCount = Math.floor(mBuffer.length / blockSize);

        for (var i = 0; i < blockCount; i++) {
            blocks.push(mBuffer.slice(i * blockSize, (i + 1) * blockSize));
        }

        // Set the buffer to the remaining bytes
        mBuffer = mBuffer.slice(blockCount * blockSize);

        return blocks;
    }

    function encryptBlocks(blocks) {

        var result = [],
            toEncrypt;

        for (var i = 0; i < blocks.length; i++) {
            toEncrypt = msrcryptoUtilities.xorVectors(mIvBytes, blocks[i]);
            result.push(blockCipher.encrypt(toEncrypt));
            mIvBytes = result[i];
        }

        return result;
    }

    function decryptBlocks(blocks) {

        var result = [],
            toDecrypt,
            decrypted;

        for (var i = 0 ; i < blocks.length; i += 1) {
            toDecrypt = blocks[i].slice(0, blocks[i].length);
            decrypted = blockCipher.decrypt(toDecrypt);
            result.push(msrcryptoUtilities.xorVectors(mIvBytes, decrypted));
            mIvBytes = blocks[i];
        }

        return result;
    }

    function clearState() {
        mBuffer = [];
        mResultBuffer = [];
        mIvBytes = null;
    }

    var mBuffer = [],
        mResultBuffer = [],
        mIvBytes;

    return {

        init: function (ivBytes) {

            if (ivBytes.length !== blockSize) {
                throw new Error("Invalid iv size");
            }

            mIvBytes = ivBytes.slice();
        },

        // Does a full encryption of the input
        encrypt: function (plainBytes) {
            /// <summary>perform the encryption of the plain text message</summary>
            /// <param name="plainBytes" type="Array">the plain text to encrypt</param>
            /// <returns type="Array">the encrypted message</returns>

            this.processEncrypt(plainBytes);

            return this.finishEncrypt();
        },

        // Encrypts full blocks of streamed input
        processEncrypt: function (plainBytes) {

            var result = encryptBlocks(getBlocks(plainBytes));

            mResultBuffer = mResultBuffer.concat(mergeBlocks(result));

            return;
        },

        // Call when done streaming input
        finishEncrypt: function () {

            var blocks = mBuffer.length === 1 ? [[mBuffer[0]]] : [mBuffer];

            paddingScheme.pad(blocks);

            var result = mResultBuffer.concat(mergeBlocks(encryptBlocks(blocks)));

            clearState();

            return result;
        },

        // Does a full decryption and returns the result
        decrypt: function ( cipherBytes) {
            /// <summary>perform the decryption of the encrypted message</summary>
            /// <param name="encryptedBytes" type="Array">the plain text to encrypt</param>
            /// <returns type="Array">the encrypted message</returns>

            this.processDecrypt(cipherBytes);

            return this.finishDecrypt();
        },

        // Decrypts full blocks of streamed data
        processDecrypt: function (cipherBytes) {

            var result = decryptBlocks(getBlocks(cipherBytes));

            mResultBuffer = mResultBuffer.concat(mergeBlocks(result));

            return;
        },

        // Called to finalize streamed decryption
        finishDecrypt: function () {

            var result = mResultBuffer;

            // Strip the padding.
            var verified = paddingScheme.unpad(result);

            clearState();

            return result;
        }

    };
};

var cbcInstance = null;

if (typeof operations !== "undefined") {

    msrcryptoCbc.workerEncrypt = function (p) {

        var result;

        if (!cbcInstance) {
            cbcInstance = msrcryptoCbc(msrcryptoBlockCipher.aes(p.keyData));
            cbcInstance.init(p.algorithm.iv);
        }

        if (p.operationSubType === "process") {
            cbcInstance.processEncrypt(p.buffer);
            return;
        }

        if (p.operationSubType === "finish") {
            result = cbcInstance.finishEncrypt();
            cbcInstance = null;
            return result;
        }

        result = cbcInstance.encrypt(p.buffer);
        cbcInstance = null;
        return result;
    };

    msrcryptoCbc.workerDecrypt = function (p) {

        var result;

        if (!cbcInstance) {
            cbcInstance = msrcryptoCbc(msrcryptoBlockCipher.aes(p.keyData));
            cbcInstance.init(p.algorithm.iv);
        }

        if (p.operationSubType === "process") {
            cbcInstance.processDecrypt(p.buffer);
            return;
        }

        if (p.operationSubType === "finish") {
            result = cbcInstance.finishDecrypt();
            cbcInstance = null;
            return result;
        }

        result = cbcInstance.decrypt(p.buffer);
        cbcInstance = null;
        return result;
    };

    msrcryptoCbc.generateKey = function (p) {

        if (p.algorithm.length % 8 !== 0) {
            throw new Error();
        }

        return {
            type: "keyGeneration",
            keyData: msrcryptoPseudoRandom.getBytes(Math.floor(p.algorithm.length / 8)),
            keyHandle: {
                algorithm: p.algorithm,
                extractable: p.extractable,
                keyUsage: null || p.keyUsage,
                type: "secret"
            }
        };
    };

    msrcryptoCbc.importKey = function (p) {

        var keyObject = msrcryptoJwk.jwkToKey(p.keyData, p.algorithm, ["k"]);

        p.algorithm.length = keyObject.k.length * 8;

        return {
            type: "keyImport",
            keyData: keyObject.k,
            keyHandle: {
                algorithm: p.algorithm,
                extractable: p.extractable || keyObject.extractable,
                keyUsage: null || p.keyUsage,
                type: "secret"
            }
        };
    };

    msrcryptoCbc.exportKey = function (p) {

        var jsonKeyStringArray = msrcryptoJwk.keyToJwk(p.keyHandle, p.keyData);

        return { type: "keyExport", keyHandle: jsonKeyStringArray };
    };

    operations.register("importKey", "aes-cbc", msrcryptoCbc.importKey);
    operations.register("exportKey", "aes-cbc", msrcryptoCbc.exportKey);
    operations.register("generateKey", "aes-cbc", msrcryptoCbc.generateKey);
    operations.register("encrypt", "aes-cbc", msrcryptoCbc.workerEncrypt);
    operations.register("decrypt", "aes-cbc", msrcryptoCbc.workerDecrypt);
}

var msrcryptoGcm = function ( blockCipher) {

    var utils = msrcryptoUtilities;

    var mBuffer = [], mIvBytes, mAdditionalBytes, mTagLength, mJ0, mJ0inc, mH = blockCipher.encrypt(utils.getVector(16)), mGHashState = utils.getVector(16), mGHashBuffer = [], mCipherText = [], mGctrCb, mBytesProcessed = 0;

    function ghash( hashSubkey, dataBytes) {

        var blockCount = Math.floor(dataBytes.length / 16),
            dataBlock;

        for (var i = 0; i < blockCount; i++) {
            dataBlock = dataBytes.slice(i * 16, i * 16 + 16);
            mGHashState = blockMultiplication(utils.xorVectors(mGHashState, dataBlock), hashSubkey);
        }

        mGHashBuffer = dataBytes.slice(blockCount * 16);

        return mGHashState;
    }

    function finishGHash() {

        var u = 16 * Math.ceil(mBytesProcessed / 16) - mBytesProcessed;

        var lenA = numberTo8Bytes(mAdditionalBytes.length * 8),
            lenC = numberTo8Bytes(mBytesProcessed * 8);

        var p = mGHashBuffer.concat(utils.getVector(u)).concat(lenA).concat(lenC);

        return ghash(mH, p);

    }

    function blockMultiplication( blockX, blockY) {

        var z = utils.getVector(16),
            v = blockY.slice(),
            r = [0xe1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
            bit;

        for (var i = 0; i < 128; i++) {
            bit = getBit(blockX, i);

            if (bit === 1) {
                z = utils.xorVectors(z, v);
            }

            if (v[15] & 1) {
                shiftRight(v);
                v = utils.xorVectors(v, r);
            } else {
                shiftRight(v);
            }
        }

        return z;
    }

    function shiftRight( dataBytes) {

        for (var i = dataBytes.length - 1; i > 0; i--) {
            dataBytes[i] = ((dataBytes[i - 1] & 1) << 7) | (dataBytes[i] >>> 1);
        }
        dataBytes[0] = dataBytes[0] >>> 1;

        return dataBytes;
    }

    function getBit( byteArray, bitNumber) {
        var byteIndex = Math.floor(bitNumber / 8);
        return (byteArray[byteIndex] >> (7 - (bitNumber % 8))) & 1;
    }

    function inc( dataBytes) {

        var carry = 256;
        for (var i = 1; i <= 4; i++) {
            carry = (carry >>> 8) + dataBytes[dataBytes.length - i];
            dataBytes[dataBytes.length - i] = carry & 255;
        }

        return dataBytes;
    }

    function gctr( icb, dataBytes) {

        var blockCount = Math.ceil(dataBytes.length / 16),
            dataBlock,
            result = [];

        // We copy icb the first time gctr is called
        if (mGctrCb !== icb) {
            mGctrCb = icb.slice();
        }

        for (var block = 0; block < blockCount; block++) {

            dataBlock = dataBytes.slice(block * 16, block * 16 + 16);

            // The block cipher alters the input array, so we pass a copy.
            var e = blockCipher.encrypt(mGctrCb.slice());

            result = result.concat(utils.xorVectors(dataBlock, e));

            mGctrCb = inc(mGctrCb);
        }

        return result;
    }

    function numberTo8Bytes(number) {
        return [
            0, 0, 0, 0,
            (number >>> 24) & 255,
            (number >>> 16) & 255,
            (number >>> 8) & 255,
            number & 255
        ];
    }

    function padBlocks( dataBytes) {
        var padLen = 16 * Math.ceil(mAdditionalBytes.length / 16) - mAdditionalBytes.length;
        return dataBytes.concat(utils.getVector(padLen));
    }

    function clearState() {
        mBytesProcessed = 0;
        mBuffer = [];
        mCipherText = [];
        mGHashState = utils.getVector(16);
        mGHashBuffer = [];
        mGctrCb = mIvBytes = mAdditionalBytes = null;
    }

    function init( ivBytes, additionalBytes, tagLength) {

        mAdditionalBytes = additionalBytes || [];

        mTagLength = isNaN(tagLength) ? 128 : tagLength;
        if (mTagLength % 8 !== 0) {
            throw new Error("DataError");
        }

        mIvBytes = ivBytes;

        if (mIvBytes.length === 12) {
            mJ0 = mIvBytes.concat([0, 0, 0, 1]);

        } else {
            var l = 16 * Math.ceil(mIvBytes.length / 16) - mIvBytes.length;

            mJ0 = ghash(mH,
                    mIvBytes
                    .concat(utils.getVector(l + 8))
                    .concat(numberTo8Bytes(mIvBytes.length * 8)));

            // Reset the ghash state so we don't affect the encrypt/decrypt ghash
            mGHashState = utils.getVector(16);
        }

        mJ0inc = inc(mJ0.slice());

        ghash(mH, padBlocks(mAdditionalBytes));
    }

    function encrypt( plainBytes) {

        mBytesProcessed = plainBytes.length;

        var c = gctr(mJ0inc, plainBytes);

        ghash(mH, c);

        var s = finishGHash();

        var t = gctr(mJ0, s).slice(0, mTagLength / 8);

        clearState();

        return c.slice().concat(t);
    }

    function decrypt( cipherBytes, tagBytes) {

        mBytesProcessed = cipherBytes.length;

        var p = gctr(mJ0inc, cipherBytes);

        ghash(mH, cipherBytes);

        var s = finishGHash();

        var t = gctr(mJ0, s).slice(0, mTagLength / 8);

        clearState();

        if (utils.arraysEqual(t, tagBytes)) {
            return p;
        } else {
            return null;
        }
    }

    function processEncrypt( plainBytes) {

        // Append incoming bytes to the end of the existing buffered bytes
        mBuffer = mBuffer.concat(plainBytes);

        // Get a run of full blocks
        var fullBlocks = mBuffer.slice(0, Math.floor(mBuffer.length / 16) * 16);

        // Keep track of the total plain bytes processed
        mBytesProcessed += fullBlocks.length;

        // Set the buffer to the remaining bytes
        mBuffer = mBuffer.slice(fullBlocks.length);

        // Process the full block with gctr - gctr maintains it's own state
        var c = gctr(mGctrCb || mJ0inc, fullBlocks);

        mCipherText = mCipherText.concat(c);

        // Process the returned blocks from gcrt
        ghash(mH, c);
    }

    function processDecrypt( cipherBytes) {

        // Append incoming bytes to the end of the existing buffered bytes
        mBuffer = mBuffer.concat(cipherBytes);

        // Get a run of full blocks.
        // We leave enough data on the end so we don't process the tag.
        var fullBlocks = mBuffer.slice(0, Math.floor((mBuffer.length - mTagLength / 8) / 16) * 16);

        // Keep track of the total plain bytes processed
        mBytesProcessed += fullBlocks.length;

        // Set the buffer to the remaining bytes
        mBuffer = mBuffer.slice(fullBlocks.length);

        // Process the full block with gctr - gctr maintains it's own state
        var c = gctr(mGctrCb || mJ0inc, fullBlocks);

        mCipherText = mCipherText.concat(c);

        // Process the returned blocks from gcrt
        ghash(mH, fullBlocks);
    }

    function finishEncrypt() {

        var c = gctr(mGctrCb, mBuffer);

        mCipherText = mCipherText.concat(c);

        mBytesProcessed += mBuffer.length;

        var s = finishGHash();

        var t = gctr(mJ0, s).slice(0, mTagLength / 8);

        var result = mCipherText.slice().concat(t);

        clearState();

        return result;
    }

    function finishDecrypt() {

        var tagLength = Math.floor(mTagLength / 8);

        var tagBytes = mBuffer.slice( -tagLength);

        mBuffer = mBuffer.slice(0, mBuffer.length - tagLength);

        var c = gctr(mGctrCb, mBuffer);

        mCipherText = mCipherText.concat(c);

        mBytesProcessed += mBuffer.length;

        var s = finishGHash();

        var t = gctr(mJ0, s).slice(0, mTagLength / 8);

        var result = mCipherText.slice();

        clearState();

        if (utils.arraysEqual(t, tagBytes)) {
            return result;
        } else {
            throw new Error("OperationError");
        }
    }

    return {
        init: init,
        encrypt: encrypt,
        decrypt: decrypt,
        processEncrypt: processEncrypt,
        processDecrypt: processDecrypt,
        finishEncrypt: finishEncrypt,
        finishDecrypt: finishDecrypt
    };

};

var gcm;

if (typeof operations !== "undefined") {

    msrcryptoGcm.encrypt = function ( p) {

        var result;

        if (!gcm) {
            gcm = msrcryptoGcm(msrcryptoBlockCipher.aes(p.keyData));
            gcm.init(p.algorithm.iv, p.algorithm.additionalData, p.algorithm.tagLength);
        }

        if (p.operationSubType === "process") {
            gcm.processEncrypt(p.buffer);
            return;
        }

        if (p.operationSubType === "finish") {
            result = gcm.finishEncrypt();
            gcm = null;
            return result;
        }

        result = gcm.encrypt(p.buffer);
        gcm = null;
        return result;
    };

    msrcryptoGcm.decrypt = function ( p) {

        var result;

        if (!gcm) {
            gcm = msrcryptoGcm(msrcryptoBlockCipher.aes(p.keyData));
            gcm.init(p.algorithm.iv, p.algorithm.additionalData, p.algorithm.tagLength);
        }

        if (p.operationSubType === "process") {
            gcm.processDecrypt(p.buffer);
            return;
        }

        if (p.operationSubType === "finish") {
             result = gcm.finishDecrypt();
            gcm = null;
            return result;
        }

        var tagLength = Math.floor(p.algorithm.tagLength / 8);
        var cipherBytes = p.buffer.slice(0, p.buffer.length - tagLength);
        var tagBytes = p.buffer.slice( -tagLength);

        result = gcm.decrypt(cipherBytes, tagBytes);
        gcm = null;
        return result;
    };

    msrcryptoGcm.generateKey = function ( p) {

        if (p.algorithm.length % 8 !== 0) {
            throw new Error();
        }

        return {
            type: "keyGeneration",
            keyData: msrcryptoPseudoRandom.getBytes(Math.floor(p.algorithm.length / 8)),
            keyHandle: {
                algorithm: p.algorithm,
                extractable: p.extractable,
                keyUsage: null || p.keyUsage,
                type: "secret"
            }
        };
    };

    msrcryptoGcm.importKey = function ( p) {

        var keyObject = msrcryptoJwk.jwkToKey(p.keyData, p.algorithm, ["k"]);

        return {
            type: "keyImport",
            keyData: keyObject.k,
            keyHandle: {
                algorithm: p.algorithm,
                extractable: p.extractable || keyObject.extractable,
                keyUsage: null || p.keyUsage,
                type: "secret"
            }
        };
    };

    msrcryptoGcm.exportKey = function ( p) {

        var jsonKeyStringArray = msrcryptoJwk.keyToJwk(p.keyHandle, p.keyData);

        return { type: "keyExport", keyHandle: jsonKeyStringArray };
    };

    operations.register("importKey", "aes-gcm", msrcryptoGcm.importKey);
    operations.register("exportKey", "aes-gcm", msrcryptoGcm.exportKey);
    operations.register("generateKey", "aes-gcm", msrcryptoGcm.generateKey);
    operations.register("encrypt", "aes-gcm", msrcryptoGcm.encrypt);
    operations.register("decrypt", "aes-gcm", msrcryptoGcm.decrypt);
} function MsrcryptoPrng() {
    /// <summary>Pseudo Random Number Generator function/class.</summary>
    /// <remarks>This is the PRNG engine, not the entropy collector.
    /// The engine must be initialized with adequate entropy in order to generate cryptographically secure
    /// random numbers. It is hard to get entropy, but see the entropy functoin/class for the entropy gatherer.
    /// This is not an object instantiation, but the definition of the object. The actual
    /// object must be instantiated somewhere else as needed.
    /// </remarks>

    if (!(this instanceof MsrcryptoPrng)) {
        throw new Error("create MsrcryptoPrng object with new keyword");
    }

    // Fallback for browsers which do not implements crypto API yet
    // implementation of http://csrc.nist.gov/publications/nistpubs/800-90A/SP800-90A.pdf.
    // Use AES-256 in CTR mode of operation as defined in Section 10.2.1.
    var initialized = false;

    // Internal state definitions are as follows.
    // v : internal variable that will ultimately be the random output
    // key: the AES key (256 bits)
    // keyLen: the AES key length in bytes
    // reseedCounter: the number of requests for pseudorandom bits since instantiation/reseeding
    // reseedInterval: Maximum number of generate calls per seed or reseed. SP800-90A says 2^48 for AES, we use 2^24.
    var key;
    var v;
    var keyLen;
    var seedLen;
    var reseedCounter = 1;
    var reseedInterval = 1 << 24;

    // Initialize this instance (constructor like function)
    initialize();

    function addOne(counter) {
        /// <summary>Adds one to a big integer represented in an array (the first argument).</summary>
        /// <param name="counter" counter="Array">The counter byte array to add one to encoded in big endian; index 0 is the MSW.</param>
        var i;
        for (i = counter.length - 1; i >= 0; i -= 1) {
            counter[i] += 1;
            if (counter[i] >= 256) {
                counter[i] = 0;
            }
            if (counter[i]) {
                break;
            }
        }
    }

    function initialize() {
        /// <summary>Instantiate the PRNG with given entropy and personalization string.</summary>
        /// <param name="entropy" type="Array">Array of bytes obtained from the source of entropy input.</param>
        /// <param name="personalizationString" type="Array">Optional application-provided personalization string.</param>
        key = msrcryptoUtilities.getVector(32);
        v = msrcryptoUtilities.getVector(16);            // AES block length
        keyLen = 32;
        seedLen = 48;       // From SP800-90A, section 10.2.1 as of 2014.
        reseedCounter = 1;
    }

    function reseed(entropy, additionalEntropy) {
        /// <summary>Reseed the PRNG with additional entropy.</summary>
        /// <param name="entropy" type="Array">Input entropy.</param>
        /// <param name="additionalEntropy" type="Array">Optional additional entropy input.</param>
        additionalEntropy = additionalEntropy || [0];
        if (additionalEntropy.length > seedLen) {
            throw new Error("Incorrect entropy or additionalEntropy length");
        }
        additionalEntropy = additionalEntropy.concat(msrcryptoUtilities.getVector(seedLen - additionalEntropy.length));

        // Process the entropy input in blocks with the same additional entropy.
        // This is equivalent to the caller chunking entropy in blocks and calling this function for each chunk.
        entropy = entropy.concat(msrcryptoUtilities.getVector((seedLen - (entropy.length % seedLen)) % seedLen));
        for (var i = 0; i < entropy.length; i += seedLen) {
            var seedMaterial = msrcryptoUtilities.xorVectors(entropy.slice(i, i + seedLen), additionalEntropy);
            update(seedMaterial);
        }
        reseedCounter = 1;
    }

    function update(providedData) {
        /// <summary>Add the providedData to the internal entropy pool, and update internal state.</summary>
        /// <param name="providedData" type="Array">Input to add to the internal entropy pool.</param>
        var temp = [];
        var blockCipher = new msrcryptoBlockCipher.aes(key);
        while (temp.length < seedLen) {
            addOne(v);
            var toEncrypt = v.slice(0, 16);
            var outputBlock = blockCipher.encrypt(toEncrypt); // AES-256
            temp = temp.concat(outputBlock);
        }
        temp = msrcryptoUtilities.xorVectors(temp, providedData);
        key = temp.slice(0, keyLen);
        v = temp.slice(keyLen);
    }

    function generate(requestedBytes, additionalInput) {
        /// <summary>Generate pseudo-random bits, and update the internal PRNG state.</summary>
        /// <param name="requestedBytes" type="Number">Number of pseudorandom bytes to be returned.</param>
        /// <param name="additionalInput" type="Array">Application-provided additional input array (optional).</param>
        /// <returns>Generated pseudorandom bytes.</returns>
        if (requestedBytes >= 65536) {
            throw new Error("too much random requested");
        }
        if (reseedCounter > reseedInterval) {
            throw new Error("Reseeding is required");
        }
        if (additionalInput && additionalInput.length > 0) {
            while (additionalInput.length < seedLen) {
                additionalInput = additionalInput.concat(msrcryptoUtilities.getVector(seedLen - additionalInput.length));
            }
            update(additionalInput);
        } else {
            additionalInput = msrcryptoUtilities.getVector(seedLen);
        }
        var temp = [];
        var blockCipher = new msrcryptoBlockCipher.aes(key);
        while (temp.length < requestedBytes) {
            addOne(v);
            var toEncrypt = v.slice(0, v.length);
            var outputBlock = blockCipher.encrypt(toEncrypt);
            temp = temp.concat(outputBlock);
        }
        temp = temp.slice(0, requestedBytes);
        update(additionalInput);
        reseedCounter += 1;
        return temp;
    }

    return {
        reseed: reseed,
        /// <summary>Reseed the PRNG with additional entropy.</summary>
        /// <param name="entropy" type="Array">Input entropy.</param>
        /// <param name="additionalEntropy" type="Array">Optional additional entropy input.</param>

        init: function (entropy, personalization) {
            /// <summary>Initialize the PRNG by seeing with entropy and optional input data.</summary>
            /// <param name="entropy" type="Array">Input entropy.</param>
            /// <param name="personalization" type="Array">Optional input.</param>
            if (entropy.length < seedLen) {
                throw new Error("Initial entropy length too short");
            }
            initialize();
            reseed(entropy, personalization);
            initialized = true;
        },
        getBytes: function (length, additionalInput) {
            if (!initialized) {
                throw new Error("can't get randomness before initialization");
            }
            return generate(length, additionalInput);
        },
        getNonZeroBytes: function (length, additionalInput) {
            if (!initialized) {
                throw new Error("can't get randomness before initialization");
            }
            var result = [], buff;
            while (result.length < length) {
                buff = generate(length, additionalInput);
                for (var i = 0 ; i < buff.length; i += 1) {
                    if (buff[i] !== 0) {
                        result.push(buff[i]);
                    }
                }
            }
            return result.slice(0, length);
        }
    };
}

// This is the PRNG object per instantiation, including one per worker.
// The instance in the main thread is used to seed the instances in workers.
// TODO: Consider combining the entropy pool in the main thread with the PRNG instance in the main thread.
var msrcryptoPseudoRandom = new MsrcryptoPrng();

function MsrcryptoEntropy() {
    /// <summary>Opportunistic entropy collector.</summary>
    /// <remarks>See E.Stark, M.Hamburg, D.Boneh, "Symmetric Cryptography in Javascript", ACSAC, 2009.
    /// This is not an object instantiation, but the definition of the object. The actual
    /// object must be instantiated somewhere else as needed.
    /// If window.{crypto,msCrypto}.getRandomValues() function is present, do not register mouse and JS load events,
    /// because they slow down the execution, and it is not clear how much they contributed over and above
    /// a cryptographic random value.
    /// </remarks>

    var poolLength = 48;      // In bytes, from SP800-90A, Section 10.2.1. See random.js for constraints.
    var collectorPool = [];
    var collectorPoolLength = 128;  // Bytes to collect before stopping; collectors are restartable.
    var collectorsRegistered = 0;
    var entropyPoolPrng = new MsrcryptoPrng();
    var initialized = false;
    var cryptographicPRNGPresent = false;
    var headerList = ["Cookie", "RedirectUri", "ETag", "x-ms-client-antiforgery-id", "x-ms-client-request-id", "x-ms-client-session-id", "SubscriptionPool"];

    function collectEntropy() {
        /// <summary>Initialize the internal pool with as much randomness as one can get in JS.
        /// In the worst case, there is zero bits of entropy.</summary>

        var i, pool = [];

        // In Safari, as of r39510, reportedly, Math.random() is cryptographically secure on Mac and Windows.
        // Even if it isn't, mix that in via XORing into the existing array.
        // According to ECMA, Math.random() returns [0,1). Thus, multiply it by 256 to get [0,256).
        for (i = 0; i < poolLength; i += 1) {
            pool[i] = Math.floor(Math.random() * 256);
        }

        // For browsers that implement window.crypto.getRandomValues, use it.
        var prngCrypto = window.crypto || window.msCrypto;       // WARNING: !!! Do not put this in a function (remember polyfill) !!!
        if (prngCrypto && typeof prngCrypto.getRandomValues === "function") {
            if (window.Uint8Array) {
                var res = new window.Uint8Array(poolLength);
                prngCrypto.getRandomValues(res);
                pool = pool.concat(Array.apply(null, res));
                cryptographicPRNGPresent = true;
            }
        }

        // Read HTTP headers that contain entropy and reseed the entropy pool
        var req = new XMLHttpRequest();
        for (i = 0; i < headerList.length; i += 1) {
            try {
                var header = req.getResponseHeader(headerList[i]);
                if (header) {
                    var arr = msrcryptoUtilities.stringToBytes(header);
                    pool = pool.concat(arr);
                }
            }
            catch (err) {
                // Ignore any header I can't get
            }
        }

        if (!cryptographicPRNGPresent) {
            // Add any data in the collector pool, empty the collector pool, and restart collectors.
            pool = pool.concat(collectorPool.splice(0, collectorPool.length));
            collectors.startCollectors();
        }

        // Worst case: initialized with Math.random()
        initialized ? entropyPoolPrng.reseed(pool) : entropyPoolPrng.init(pool);
        initialized = true;
    }

    function updatePool(entropyData) {
        /// <summary>Collect the incoming data into the pool, and
        /// empty the pool into the entropy PRNG state when the pool is full.
        /// This function is additive entropy, only; this is not the main source of entropy.</summary>
        /// <param name="entropyData" type="Array">Entropy input.</param>
        for (var i = 0; i < entropyData.length; ++i) {
            collectorPool.push(entropyData[i]);
        }
        if (collectorPool.length >= collectorPoolLength) {
            // Stop the collectors (performance reasons).
            // The real entropy does not come from the event callbacks: these are at best uniquifiers.
            collectors.stopCollectors();
        }
    }

    // Event listeners are not supported in IE 8.
    // See https://developer.mozilla.org/en-US/docs/Web/API/EventTarget.addEventListener?redirectlocale=en-US&redirectslug=DOM%2FEventTarget.addEventListener
    // to add IE8 support.
    // BUGBUG: For the time being, I am not bothering with IE8 support - fix this.
    var collectors = (function () {
        return {
            startCollectors: function () {
                if (!this.collectorsRegistered) {
                    if (window.addEventListener) {
                        window.addEventListener("mousemove", this.MouseEventCallBack, true);
                        window.addEventListener("load", this.LoadTimeCallBack, true);
                    } else if (document.attachEvent) {
                        document.attachEvent("onmousemove", this.MouseEventCallBack);
                        document.attachEvent("onload", this.LoadTimeCallBack);
                    } else {
                        throw new Error("Can't attach events for entropy collection");
                    }

                    this.collectorsRegistered = 1;
                }
            },
            stopCollectors: function () {
                if (this.collectorsRegistered) {
                    if (window.removeEventListener) {
                        window.removeEventListener("mousemove", this.MouseEventCallBack, 1);
                        window.removeEventListener("load", this.LoadTimeCallBack, 1);
                    } else if (window.detachEvent) {
                        window.detachEvent("onmousemove", this.MouseEventCallBack);
                        window.detachEvent("onload", this.LoadTimeCallBack);
                    }

                    this.collectorsRegistered = 0;
                }
            },
            MouseEventCallBack: function (eventData) {
                /// <summary>Add the mouse coordinates to the entropy pool and the Date.</summary>
                /// <param name="eventData">Event data with mouse information.</param>
                var d = (new Date()).valueOf();
                var x = eventData.x || eventData.clientX || eventData.offsetX || 0;
                var y = eventData.y || eventData.clientY || eventData.offsetY || 0;
                var arr = [d & 0x0ff, (d >> 8) & 0x0ff, (d >> 16) & 0x0ff, (d >> 24) & 0x0ff,
                        x & 0x0ff, (x >> 8) & 0x0ff, y & 0x0ff, (y >> 8) & 0x0ff];

                updatePool(arr);
            },
            LoadTimeCallBack: function () {
                /// <summary>Add date to the entropy pool.</summary>
                /// <remarks>Date valueOf() returns milliseconds since midnight 1/1/1970 UTC in a 32 bit integer</remarks>
                var d = (new Date()).valueOf();
                var dateArray = [d & 0x0ff, (d >> 8) & 0x0ff, (d >> 16) & 0x0ff, (d >> 24) & 0x0ff];

                updatePool(dateArray);
            }
        };
    })();

    return {
        init: function () {
            collectEntropy();

            // Register collectors
            if (!cryptographicPRNGPresent && !collectorsRegistered) {
                try {
                    collectors.startCollectors();
                }
                catch (e) {
                    // Ignore errors instead of trying to do something browser specific. That is not tractable.
                    // It is expected that the calling program injects most of the entropy or the build-in collectors
                    // contributes rather than registered events.
                }
            }
        },

        reseed: function (entropy) {
            /// <summary>Mix in entropy into the pool.</summary>
            /// <param name="entropy" type="Array">Entropy to mix in.</param>
            entropyPoolPrng.reseed(entropy);
        },

        read: function (length) {
            /// <summary>Read entropy from the entropy pool. This function fails if there isn't enough entropy.</summary>
            /// <param name="length" type="Number">Number of bytes of requested entropy.</param>
            /// <returns type="Array">Entropy if there is enough in the pool, or undefined if there isn't enough entropy.</returns>
            if (!initialized) {
                throw new Error("Entropy pool is not initialized.");
            }

            var ret = entropyPoolPrng.getBytes(length);

            // TODO: Do this async?
            //       No, another call may come through before the pool is reseeded.
            //       All PRNGs have their own running state anyhow. They can reseed themselves in async mode, if need be.
            collectEntropy();

            return ret;
        }
    };
}

var msrcryptoRsaBase = function (keyStruct) {

    var utils = msrcryptoUtilities,
        keyIsPrivate = keyStruct.hasOwnProperty("n") && keyStruct.hasOwnProperty("d"),
        keyIsCrt = keyStruct.hasOwnProperty("p") && keyStruct.hasOwnProperty("q"),
        modulusLength = keyStruct.n.length;

    function toBytes(digits) {

        var bytes = cryptoMath.digitsToBytes(digits);

        // Add leading zeros until the message is the proper length.
        utils.padFront(bytes, 0, modulusLength);

        return bytes;
    }

    function modExp( dataBytes, expBytes, modulusBytes) {
        /// <returns type="Array">Result in a digit array.</returns>
        var exponent = cryptoMath.bytesToDigits(expBytes);

        var group = cryptoMath.IntegerGroup(modulusBytes);
        var base = group.createElementFromBytes(dataBytes);
        var result = group.modexp(base, exponent);

        return result.m_digits;
    }

    function decryptModExp(cipherBytes) {

        var resultElement = modExp(cipherBytes, keyStruct.d, keyStruct.n);

        return toBytes(resultElement);
    }

    function decryptCrt(cipherBytes) {

        var p = keyStruct.p,
            q = keyStruct.q,
            dp = keyStruct.dp,
            dq = keyStruct.dq,
            invQ = keyStruct.qi,
            pDigits = cryptoMath.bytesToDigits(p),
            qDigits = cryptoMath.bytesToDigits(q),
            temp = new Array(pDigits.length + qDigits.length),
            m1Digits = new Array(pDigits.length + 1),
            m2Digits = new Array(qDigits.length + 1),
            cDigits = cryptoMath.bytesToDigits(cipherBytes);

        // 'm1' = (c mod p)^dP mod p
        cryptoMath.reduce(cDigits, pDigits, temp);
        cryptoMath.modExp(temp, cryptoMath.bytesToDigits(dp), pDigits, m1Digits);

        // 'm2' = (c mod q)^dQ mod q
        cryptoMath.reduce(cDigits, qDigits, temp);
        cryptoMath.modExp(temp, cryptoMath.bytesToDigits(dq), qDigits, m2Digits);

        // 'diff' = (m1 - m2). Compute as follows to have |m1 - m2|.
        //      m1 - m2     if m1>=m2.
        //      m2 - m1     if m1<m2.
        // Correct the sign after modular multiplication by qInv by subtracting the product from p.
        var carry = cryptoMath.subtract(m1Digits, m2Digits, temp);
        if (carry !== 0) {
            cryptoMath.subtract(m2Digits, m1Digits, temp);
        }

        // 'h' = (m1 - m2)^qInv mod p
        cryptoMath.modMul(temp, cryptoMath.bytesToDigits(invQ), pDigits, cDigits);
        if (carry !== 0) {
            cryptoMath.subtract(pDigits, cDigits, cDigits);
        }

        // 'm2' + q*h
        cryptoMath.multiply(cDigits, qDigits, temp);
        cryptoMath.add(m2Digits, temp, m1Digits);

        return toBytes(m1Digits);
    }

    return {

        encrypt: function (messageBytes) {

            return toBytes(modExp(messageBytes, keyStruct.e, keyStruct.n));

        },

        decrypt: function (cipherBytes) {

            if (keyIsCrt) {
                return decryptCrt(cipherBytes);
            }

            if (keyIsPrivate) {
                return decryptModExp(cipherBytes);
            }

            throw new Error("missing private key");
        }
    };

};

var rsaShared = {

    mgf1: function (seedBytes, maskLen, hashFunction) {

        var t = [], bytes, hash, counter,
            hashByteLen = hashFunction.hashLen / 8;

        for (counter = 0; counter <= Math.floor(maskLen / hashByteLen) ; counter += 1) {

            bytes = [counter >>> 24 & 0xff,
                    counter >>> 16 & 0xff,
                    counter >>> 8 & 0xff,
                    counter & 0xff];

            hash = hashFunction.computeHash(seedBytes.concat(bytes));

            t = t.concat(hash);
        }

        return t.slice(0, maskLen);
    },

    checkMessageVsMaxHash: function (messageBytes, hashFunction) {

        // The max array size in JS is 2^32-1
        if (messageBytes.length > (hashFunction.maxMessageSize || 0xFFFFFFFF)) {
            throw new Error("message too long");
        }

        return;
    }

};

var rsaMode = rsaMode || {};

rsaMode.oaep = function (keyStruct, hashFunction) {

    var utils = msrcryptoUtilities,
        random = msrcryptoPseudoRandom,
        size = keyStruct.n.length;

    if (hashFunction === null) {
        throw new Error("must supply hashFunction");
    }

    function pad( message, label) {

        var lHash, psLen, psArray, i, db, seed;
        var dbMask, maskeddb, seedMask, maskedSeed;
        var encodedMessage;

        if (message.length > (size - 2 * (hashFunction.hashLen / 8) - 2)) {
            throw new Error("Message too long.");
        }

        label || (label = []);

        lHash = hashFunction.computeHash( label);

        psLen = size - message.length - (2 * lHash.length) - 2;
        psArray = utils.getVector(psLen);

        // 'db' = 'lHash' || 'psArray' || 0x01 || message
        db = lHash.concat(psArray, [1], message);

        seed = random.getBytes(lHash.length);

        dbMask = rsaShared.mgf1(seed, size - lHash.length - 1, hashFunction);

        maskeddb = utils.xorVectors(db, dbMask);

        seedMask = rsaShared.mgf1(maskeddb, lHash.length, hashFunction);

        maskedSeed = utils.xorVectors(seed, seedMask);

        encodedMessage = [0].concat(maskedSeed).concat(maskeddb);

        message = encodedMessage.slice();

        return encodedMessage;
    }

    function unpad( encodedBytes, labelBytes) {

        var lHash, maskedSeed, maskeddb, seedMask;
        var seed, dbMask, db;
        var lHashp;

        if (!labelBytes) {
            labelBytes = [];
        }

        lHash = hashFunction.computeHash(labelBytes);

        if (encodedBytes[0] !== 0) {
            throw new Error("Encryption Error");
        }

        maskedSeed = encodedBytes.slice(1, lHash.length + 1);
        maskeddb = encodedBytes.slice(lHash.length + 1);

        seedMask = rsaShared.mgf1(maskeddb, lHash.length, hashFunction);
        seed = utils.xorVectors(maskedSeed, seedMask);
        dbMask = rsaShared.mgf1(seed, size - lHash.length - 1, hashFunction);

        db = utils.xorVectors(maskeddb, dbMask);

        lHashp = db.slice(0, lHash.length);

        // lHashp should equal lHash or 'Encryption Error'
        if (!utils.arraysEqual(lHash, lHashp)) {
            throw new Error("Encryption Error");
        }

        db = db.slice(lHash.length);

        // There will be a bunch of zeros followed by a
        var i = db.indexOf(1);

        return db.slice(i + 1);
    }

    return {

        pad: function ( messageBytes, labelBytes) {
            return pad(messageBytes, labelBytes);
        },

        unpad: function ( encodedBytes, labelBytes) {
            return unpad(encodedBytes, labelBytes);
        }
    };

};

var rsaMode = rsaMode || {};

rsaMode.pkcs1Encrypt = function (keyStruct) {

    var random = msrcryptoPseudoRandom,
        size = keyStruct.n.length;

    function pad(data) {

        var randomness;

        if (data.length > size - 11) {
            throw new Error("message too long");
        }

        // A minimum of 8 random bytes
        randomness = random.getNonZeroBytes(size - data.length - 3);

        return [0, 2].concat(randomness, [0], data);
    }

    function unpad(paddedData) {
        var i;

        for (i = 1; i < paddedData.length; i += 1) {
            if (paddedData[i] === 0) {
                break;
            }
        }

        return paddedData.slice(i + 1);
    }

    return {

        pad: function (messageBytes) {
            return pad(messageBytes);
        },

        unpad: function (encodedBytes) {
            return unpad(encodedBytes);
        }
    };

};

rsaMode.pkcs1Sign = function (keyStruct, hashFunction) {

    var utils = msrcryptoUtilities,
        size = keyStruct.n.length;

    function emsa_pkcs1_v15_encode(messageBytes) {

        var paddedData,
            hash,
            tlen;

        hash = hashFunction.computeHash(messageBytes.slice());

        paddedData = hashFunction.der.concat(hash);

        tlen = paddedData.length;

        if (size < tlen + 11) {
            throw new Error("intended encoded message length too short");
        }

        return [0x00, 0x01].concat(
            utils.getVector(size - tlen - 3, 0xFF),
            [0],
            paddedData);
    }

    return {

        sign: function (messageBytes) {
            return emsa_pkcs1_v15_encode(messageBytes);
        },

        verify: function (signatureBytes, messageBytes) {
            var emp = emsa_pkcs1_v15_encode(messageBytes);

            return utils.arraysEqual(signatureBytes, emp);

        }
    };
};

var rsaMode = rsaMode || {};

rsaMode.pss = function (keyStruct, hashFunction) {

    var utils = msrcryptoUtilities,
        random = msrcryptoPseudoRandom;

    function emsa_pss_encode(messageBytes, saltLength, salt) {

        var emBits = (keyStruct.n.length * 8) - 1,
            emLen = Math.ceil(emBits / 8), mHash = hashFunction.computeHash(messageBytes);

        saltLength = salt ? salt.length : saltLength || mHash.length;

        if (emLen < (mHash.length + saltLength + 2)) {
            throw new Error("encoding error");
        } salt = salt || random.getBytes(saltLength);

        // M' = (0x) 00 00 00 00 00 00 00 00 || mHash || salt
        var mp = [0, 0, 0, 0, 0, 0, 0, 0].concat(mHash, salt);

        var h = hashFunction.computeHash(mp);

        var ps = utils.getVector(emLen - salt.length - h.length - 2);

        var db = ps.concat([1], salt);

        var dbMask = rsaShared.mgf1(h, emLen - h.length - 1, hashFunction);

        var maskedDb = utils.xorVectors(db, dbMask);

        // Set the ((8 * emLen) - emBits) of the leftmost octect in maskedDB to zero
        var mask = 0;
        for (var i = 0; i < 8 - ((8 * emLen) - emBits) ; i++) {
            mask += 1 << i;
        }
        maskedDb[0] &= mask;

        var em = maskedDb.concat(h, [0xbc]);

        return em;
    }

    function emsa_pss_verify( signatureBytes, messageBytes, saltLength) {

        var emBits = (keyStruct.n.length * 8) - 1;

        var emLen = Math.ceil(emBits / 8);

        var mHash = hashFunction.computeHash(messageBytes);

        var hLen = mHash.length;

        saltLength = saltLength || hLen;

        if (emLen < (hLen + saltLength + 2)) {
            return false;
        }

        var maskedDb = signatureBytes.slice(0, emLen - hLen - 1);

        var h = signatureBytes.slice(maskedDb.length, maskedDb.length + hLen);

        var dbMask = rsaShared.mgf1(h, emLen - hLen - 1, hashFunction);

        var db = utils.xorVectors(maskedDb, dbMask);

        // Set the leftmost 8 * emLen - emBits of db[0] to zero
        db[0] &= 0xFF >>> (8 - ((8 * emLen) - emBits));

        // Verify the leftmost bytes are zero
        for (var i = 0; i < (emLen - hLen - saltLength - 2) ; i++) {
            if (db[i] !== 0) {
                return false;
            }
        }

        if (db[emLen - hLen - saltLength - 2] !== 0x01) {
            return false;
        }

        var salt = db.slice(-saltLength);

        // M' = (0x) 00 00 00 00 00 00 00 00 || mHash || salt
        var mp = [0, 0, 0, 0, 0, 0, 0, 0].concat(mHash, salt);

        var hp = hashFunction.computeHash(mp);

        return utils.arraysEqual(hp, h);
    }

    return {

        sign: function (messageBytes, saltLength, salt) {
            return emsa_pss_encode(messageBytes, saltLength, salt);
        },

        verify: function (signatureBytes, messageBytes, saltLength) {
            return emsa_pss_verify(signatureBytes, messageBytes, saltLength);
        }
    };
};

var msrcryptoRsa = function (keyStruct, mode, hashFunction) {

    var rsaBase = msrcryptoRsaBase(keyStruct);

    if (!mode) {
        throw new Error("padding mode");
    }

    function checkHash() {
        if (!hashFunction || !hashFunction.computeHash) {
            throw new Error("missing hash function");
        }
    }

    var paddingFunction = null,
        unPaddingFunction = null;

    var padding;

    switch (mode) {

        case "rsaes-pkcs1-v1_5":
            padding = rsaMode.pkcs1Encrypt(keyStruct);
            break;

        case "rsassa-pkcs1-v1_5":
            checkHash();
            padding = rsaMode.pkcs1Sign(keyStruct, hashFunction);
            break;

        case "rsa-oaep":
            checkHash();
            padding = rsaMode.oaep(keyStruct, hashFunction);
            break;

        case "rsa-pss":
            checkHash();
            padding = rsaMode.pss(keyStruct, hashFunction);
            break;

        case "raw":
            padding = {
                pad: function (mb) { return mb; },
                unpad: function (eb) { return eb; }
            };
            break;

        default:
            throw new Error("invalid mode");
    }

    if (padding) {
        paddingFunction = padding.pad || padding.sign;
        unPaddingFunction = padding.unpad || padding.verify;
    }

    var returnObj = {

        encrypt: function ( dataBytes, labelBytes) {

            var paddedData;

            if (paddingFunction !== null) {
                // OAEP padding can take two arguments
                paddedData = paddingFunction(dataBytes, labelBytes);
            } else {
                // Slice() has optional arguments
                paddedData = dataBytes.slice();
            }

            return rsaBase.encrypt(paddedData);
        },

        decrypt: function ( cipherBytes, labelBytes) {

            var decryptedData = rsaBase.decrypt(cipherBytes);

            if (unPaddingFunction !== null) {
                // OAEP padding can take two arguments
                decryptedData = unPaddingFunction(decryptedData, labelBytes);
            } else {
                decryptedData = decryptedData.slice(0);
            }

            return decryptedData;
        },

        signData: function ( messageBytes, saltLength, salt) {

            return rsaBase.decrypt(paddingFunction(messageBytes, saltLength, salt));
        },

        verifySignature: function ( signature, messageBytes, saltLength) {

            var decryptedSig = rsaBase.encrypt(signature);

            return unPaddingFunction(decryptedSig, messageBytes, saltLength);
        },

        mode: mode
    };

    return returnObj;
};

if (typeof operations !== "undefined") {

    msrcryptoRsa.sign = function ( p) {

        var rsaObj,
            hashName = p.algorithm.hash.name,
            hashFunc = msrcryptoHashFunctions[hashName.toLowerCase()],
            saltLength = p.algorithm.saltLength,
            salt = p.algorithm.salt;

        rsaObj = msrcryptoRsa(p.keyData, p.algorithm.name, hashFunc);

        return rsaObj.signData(p.buffer, saltLength, salt);
    };

    msrcryptoRsa.verify = function ( p) {

        var hashName = p.algorithm.hash.name,
            hashFunc = msrcryptoHashFunctions[hashName.toLowerCase()],
            rsaObj,
            saltLength = p.algorithm.saltLength;

        rsaObj = msrcryptoRsa(p.keyData, p.algorithm.name, hashFunc);

        return rsaObj.verifySignature(p.signature, p.buffer, saltLength);
    };

    msrcryptoRsa.workerEncrypt = function ( p) {

        var result,
            rsaObj,
            hashFunc,
            hashName;

        switch (p.algorithm.name) {

            case "rsaes-pkcs1-v1_5":
                rsaObj = msrcryptoRsa(p.keyData, p.algorithm.name);
                result = rsaObj.encrypt(p.buffer);
                break;

            case "rsa-oaep":
                hashName = p.algorithm.hash.name;
                if (!hashName) {
                    throw new Error("unsupported hash algorithm");
                }
                hashFunc = msrcryptoHashFunctions[hashName.toLowerCase()];
                rsaObj = msrcryptoRsa(p.keyData, p.algorithm.name, hashFunc);
                result = rsaObj.encrypt(p.buffer);
                break;

            default:
                throw new Error("unsupported algorithm");
        }

        return result;
    };

    msrcryptoRsa.workerDecrypt = function ( p) {

        var result,
            rsaObj,
            hashFunc;

        switch (p.algorithm.name) {

            case "rsaes-pkcs1-v1_5":
                rsaObj = msrcryptoRsa(p.keyData, p.algorithm.name);
                result = rsaObj.decrypt(p.buffer);
                break;

            case "rsa-oaep":
                var hashName = p.algorithm.hash.name;
                if (!hashName) {
                    throw new Error("unsupported hash algorithm");
                }
                hashFunc = msrcryptoHashFunctions[hashName.toLowerCase()];
                rsaObj = msrcryptoRsa(p.keyData, p.algorithm.name, hashFunc);
                result = rsaObj.decrypt(p.buffer);
                break;

            default:
                throw new Error("unsupported algorithm");
        }

        return result;
    };

    msrcryptoRsa.importKey = function ( p) {

        var keyObject = msrcryptoJwk.jwkToKey(p.keyData, p.algorithm, ["n", "e", "d", "q", "p", "dq", "dp", "qi"]);

        return {
            type: "keyImport",
            keyData: keyObject,
            keyHandle: {
                algorithm: p.algorithm,
                extractable: p.extractable || keyObject.extractable,
                keyUsage: null || p.keyUsage, // IE11 returns null here
                type: (keyObject.d || keyObject.dq) ? "private" : "public"
            }
        };
    };

    msrcryptoRsa.exportKey = function ( p) {

        var jsonKeyStringArray = msrcryptoJwk.keyToJwk(p.keyHandle, p.keyData);

        return { type: "keyExport", keyHandle: jsonKeyStringArray };
    };

    operations.register("sign", "rsassa-pkcs1-v1_5", msrcryptoRsa.sign);
    operations.register("sign", "rsa-pss", msrcryptoRsa.sign);

    operations.register("verify", "rsassa-pkcs1-v1_5", msrcryptoRsa.verify);
    operations.register("verify", "rsa-pss", msrcryptoRsa.verify);

    operations.register("encrypt", "rsa-oaep", msrcryptoRsa.workerEncrypt);
    operations.register("encrypt", "rsaes-pkcs1-v1_5", msrcryptoRsa.workerEncrypt);

    operations.register("decrypt", "rsa-oaep", msrcryptoRsa.workerDecrypt);
    operations.register("decrypt", "rsaes-pkcs1-v1_5", msrcryptoRsa.workerDecrypt);

    operations.register("importKey", "rsa-oaep", msrcryptoRsa.importKey);
    operations.register("importKey", "rsaes-pkcs1-v1_5", msrcryptoRsa.importKey);
    operations.register("importKey", "rsassa-pkcs1-v1_5", msrcryptoRsa.importKey);
    operations.register("importKey", "rsa-pss", msrcryptoRsa.importKey);

    operations.register("exportKey", "rsa-oaep", msrcryptoRsa.exportKey);
    operations.register("exportKey", "rsaes-pkcs1-v1_5", msrcryptoRsa.exportKey);
    operations.register("exportKey", "rsassa-pkcs1-v1_5", msrcryptoRsa.exportKey);
    operations.register("exportKey", "rsa-pss", msrcryptoRsa.exportKey);

}

/// The "concat" key derivation function from NIST SP-800-56A.
var msrcryptoKdf = function (hashFunction) {

    var utils = msrcryptoUtilities;

    function deriveKey(secretBytes, otherInfo, keyOutputLength) {
        /// <summary></summary>
        /// <param name="secretBytes" type="Array"></param>
        /// <param name="otherInfo" type="Array"></param>
        /// <param name="keyOutputLength" type="Number"></param>
        /// <returns type="Array"></returns>

        var reps = Math.ceil(keyOutputLength / (hashFunction.hashLen / 8)),
            counter = 1,
            digest = secretBytes.concat(otherInfo),
            output = [];

        for (var i = 0; i < reps; i++) {

            var data = utils.int32ToBytes(counter++).concat(digest);

            var /*type(Array)*/ h = hashFunction.computeHash(data);

            output = output.concat(h);
        }

        return output.slice(0, keyOutputLength);
    }

    return {

        deriveKey: deriveKey

    };

};

var msrcryptoKdfInstance = null;

if (typeof operations !== "undefined") {

    msrcryptoKdf.deriveKey = function ( p) {

        var utils = msrcryptoUtilities;

        var hashName = p.algorithm.hash.name;

        var hashFunction = msrcryptoHashFunctions[hashName.toLowerCase()];

        msrcryptoKdfInstance = msrcryptoKdf(hashFunction);

        var alg = p.algorithm;

        var otherInfo =
            utils.toArray(alg.algorithmId).concat(
            utils.toArray(alg.partyUInfo),
            utils.toArray(alg.partyVInfo),
            utils.toArray(alg.publicInfo),
            utils.toArray(alg.privateInfo));

        var result =
            msrcryptoKdfInstance.deriveKey(p.keyData, otherInfo, p.derivedKeyType.length);

        msrcryptoKdfInstance = null;

        return {
            type: "keyDerive",
            keyData: result,
            keyHandle: {
                algorithm: p.derivedKeyType,
                extractable: p.extractable,
                keyUsage: null || p.keyUsage,
                type: "secret"
            }
        };

    };

    msrcryptoKdf.deriveBits = function ( p) {

        var hashName = p.algorithm.hash.name;

        var hashFunction = msrcryptoHashFunctions[hashName.toLowerCase()];

        msrcryptoKdfInstance = msrcryptoKdf(hashFunction);

        var alg = p.algorithm;

        var otherInfo =
            alg.algorithmId.concat(
            alg.partyUInfo,
            alg.partyVInfo,
            alg.publicInfo || [],
            alg.privateInfo || []);

        var result =
            msrcryptoKdfInstance.deriveKey(p.keyData, otherInfo, p.length);

        msrcryptoKdfInstance = null;

        return result;

    };

    operations.register("deriveKey", "concat", msrcryptoKdf.deriveKey);
    operations.register("deriveBits", "concat", msrcryptoKdf.deriveBits);

}

var msrcryptoEcdh = function (curve) {

    var btd = cryptoMath.bytesToDigits,
        dtb = cryptoMath.digitsToBytes,
        e = curve,
        ecop = new cryptoECC.EllipticCurveOperatorFp(curve);

    function generateKey(privateKeyBytes) {
        /// <summary></summary>
        /// <param name="privateKeyBytes" type="Array" optional="true">
        ///     For testing purposes we allow the key bytes to be passed in
        ///     instead of randomly generated.
        /// </param>
        /// <returns type=""></returns>

        var privateKey = [],
            randomBytes = msrcryptoPseudoRandom.getBytes(
                curve.order.length * cryptoMath.DIGIT_NUM_BYTES);

        cryptoMath.reduce(
            cryptoMath.bytesToDigits(randomBytes),
            e.order,
            privateKey);

        var publicKey = e.allocatePointStorage();

        ecop.scalarMultiply(privateKey, e.generator, publicKey);

        return {
            privateKey: {
                x: dtb(publicKey.x),
                y: dtb(publicKey.y),
                d: dtb(privateKey)
            },
            publicKey: {
                x: dtb(publicKey.x),
                y: dtb(publicKey.y)
            }
        };
    }

    function deriveBits(privateKey, publicKey, length) {

        var publicPoint = new cryptoECC.EllipticCurvePointFp(
            e, false, btd(publicKey.x), btd(publicKey.y), null, false);

        var sharedSecretPoint = e.allocatePointStorage();
        ecop.convertToJacobianForm(sharedSecretPoint);
        ecop.convertToMontgomeryForm(sharedSecretPoint);

        ecop.scalarMultiply(btd(privateKey.d), publicPoint, sharedSecretPoint);

        ecop.convertToAffineForm(sharedSecretPoint);
        ecop.convertToStandardForm(sharedSecretPoint);

        var secretBytes = cryptoMath.digitsToBytes(sharedSecretPoint.x);

        if (length && (secretBytes.length * 8) < length) {
            throw new Error("DataError");
        }

        return length ? secretBytes.slice(0, length / 8) : secretBytes;
    }

    function computePublicKey(privateKeyBytes) {

        if (!e.generator.isInMontgomeryForm) {
            ecop.convertToMontgomeryForm(e.generator);
        }

        var publicKey = e.allocatePointStorage();
        ecop.convertToJacobianForm(publicKey);
        ecop.convertToMontgomeryForm(publicKey);
        ecop.scalarMultiply(btd(privateKeyBytes), e.generator, publicKey);

        return {
            x: dtb(publicKey.x),
            y: dtb(publicKey.y)
        };
    }

    return {

        generateKey: generateKey,
        deriveBits: deriveBits,
        computePublicKey: computePublicKey
    };

};

var ecdhInstance = null;

if (typeof operations !== "undefined") {

    msrcryptoEcdh.deriveBits = function (p) {

        var curve = cryptoECC.createCurve(p.algorithm.namedCurve.toUpperCase());

        var privateKey = p.keyData;

        var publicKey = p.additionalKeyData;

        ecdhInstance = msrcryptoEcdh(curve);

        var secretBytes = ecdhInstance.deriveBits(privateKey, publicKey, p.length);

        return secretBytes;
    };

    msrcryptoEcdh.generateKey = function (p) {

        var curve = cryptoECC.createCurve(p.algorithm.namedCurve.toUpperCase());

        ecdhInstance = msrcryptoEcdh(curve);

        var keyPairData = ecdhInstance.generateKey();

        return {
            type: "keyPairGeneration",
            keyPair: {
                publicKey: {
                    keyData: keyPairData.publicKey,
                    keyHandle: {
                        algorithm: p.algorithm,
                        extractable: p.extractable,
                        keyUsage: p.keyUsage,
                        type: "public"
                    }
                },
                privateKey: {
                    keyData: keyPairData.privateKey,
                    keyHandle: {
                        algorithm: p.algorithm,
                        extractable: p.extractable,
                        keyUsage: p.keyUsage,
                        type: "private"
                    }
                }
            }
        };
    };

    msrcryptoEcdh.importKey = function (p) {

        var keyObject = msrcryptoJwk.jwkToKey(p.keyData, p.algorithm, ["x", "y", "d", "crv"]);

        // If only private key data 'd' is imported, create x and y
        if (keyObject.d && (!keyObject.x || !keyObject.y)) {

            var curve = cryptoECC.createCurve(p.algorithm.namedCurve.toUpperCase());

            ecdhInstance = msrcryptoEcdh(curve);

            var publicKey = ecdhInstance.computePublicKey(keyObject.d);

            keyObject.x = publicKey.x;
            keyObject.y = publicKey.y;
        }

        return {
            type: "keyImport",
            keyData: keyObject,
            keyHandle: {
                algorithm: p.algorithm,
                extractable: p.extractable || keyObject.extractable,
                keyUsage: p.keyUsage,
                type: (keyObject.d) ? "private" : "public"
            }
        };
    };

    msrcryptoEcdh.exportKey = function (p) {

        var jsonKeyStringArray = msrcryptoJwk.keyToJwk(p.keyHandle, p.keyData);

        return { type: "keyExport", keyHandle: jsonKeyStringArray };

    };

    operations.register("importKey", "ecdh", msrcryptoEcdh.importKey);
    operations.register("exportKey", "ecdh", msrcryptoEcdh.exportKey);
    operations.register("generateKey", "ecdh", msrcryptoEcdh.generateKey);
    operations.register("deriveBits", "ecdh", msrcryptoEcdh.deriveBits);
}

var msrcryptoEcdsa = function (curve) {

    var btd = cryptoMath.bytesToDigits,
        dtb = cryptoMath.digitsToBytes,
        ecop = new cryptoECC.EllipticCurveOperatorFp(curve),
        orderByteLength = dtb(curve.order).length,
        tedCurve = (curve.type === 1);

    function createKey(privateKeyBytes) {
        return createKeyInternal(btd(privateKeyBytes));
    }

    function createKeyInternal(privateKeyDigits) {

        var publicKey = curve.allocatePointStorage();

        ecop.scalarMultiply(privateKeyDigits, curve.generator, publicKey);

        return {
            publicKey: publicKey,
            privateKey: privateKeyDigits
        };
    }

    function generateKey(randomBytes) {

        var privateKey = [];

        if (!randomBytes) {
            randomBytes = msrcryptoPseudoRandom.getBytes(
                    curve.order.length * cryptoMath.DIGIT_NUM_BYTES);
        }

        cryptoMath.reduce(
            cryptoMath.bytesToDigits(randomBytes),
            curve.order,
            privateKey);

        return createKeyInternal(privateKey);
    }

    function getDigest(messageBytes) {

        // if the message length is longer than the curve-order, truncate.
        if (messageBytes.length > orderByteLength) {
            messageBytes.length = orderByteLength;
        }

        var digest = btd(messageBytes);

        if (tedCurve) {
            var shift = (8 - curve.rbits % 8);
            cryptoMath.shiftRight(digest, digest, shift);
        }

        cryptoMath.reduce(digest, curve.order, digest);

        return digest;
    }

    function sign(privateKey, messageBytes, ephemeralKey) {

        if (!ephemeralKey) {
            ephemeralKey = generateKey();
        }

        var r = ephemeralKey.publicKey.x,
            k = ephemeralKey.privateKey,
            d = btd(privateKey.d),
            digest = getDigest(messageBytes.slice()),
            s = [],
            tmp = [],
            signature = null;

        cryptoMath.reduce(r, curve.order, r);
        cryptoMath.modMul(r, d, curve.order, s);
        cryptoMath.add(s, digest, s);
        cryptoMath.reduce(s, curve.order, s);
        cryptoMath.modInv(k, curve.order, tmp);
        cryptoMath.modMul(s, tmp, curve.order, s);

        // ensure the bytes arrays are of the expected size
        var rBytes = msrcryptoUtilities.padFront(dtb(r, true, orderByteLength), 0, orderByteLength);
        var sBytes = msrcryptoUtilities.padFront(dtb(s, true, orderByteLength), 0, orderByteLength);
        signature = rBytes.concat(sBytes);

        return signature;
    }

    function verify(publicKey, signatureBytes, messageBytes) {

        var split = Math.floor(signatureBytes.length / 2),
            r = btd(signatureBytes.slice(0, split)),
            s = btd(signatureBytes.slice(split)),
            digest = getDigest(messageBytes.slice()),
            u1 = [],
            u2 = [];

        var publicPoint = new cryptoECC.EllipticCurvePointFp(
            curve, false, btd(publicKey.x), btd(publicKey.y), null, false);

        cryptoMath.modInv(s, curve.order, s);
        cryptoMath.modMul(digest, s, curve.order, u1);
        cryptoMath.modMul(r, s, curve.order, u2);

        var r0 = curve.allocatePointStorage();
        var r1 = curve.allocatePointStorage();

        if (tedCurve) {
            cryptoMath.add(u1, u1, u1);
            cryptoMath.add(u1, u1, u1);
            cryptoMath.reduce(u1, curve.order, u1);
            ecop.scalarMultiply(u1, curve.generator, r0, false);
            ecop.scalarMultiply(u2, publicPoint, r1, false);
            ecop.convertToExtendedProjective(r0);
            ecop.convertToExtendedProjective(r1);
            ecop.add(r1, r0, r0);
            ecop.normalize(r0);

        } else {
            ecop.scalarMultiply(u1, curve.generator, r0);
            ecop.scalarMultiply(u2, publicPoint, r1);
            ecop.convertToJacobianForm(r0);
            ecop.convertToMontgomeryForm(r0);
            ecop.convertToMontgomeryForm(r1);
            ecop.mixedAdd(r0, r1, r0);
            ecop.convertToAffineForm(r0);
            ecop.convertToStandardForm(r0);
        }

        if (r0.isInfinity) {
            return false;
        }

        cryptoMath.reduce(r0.x, curve.order, r0.x);

        return (cryptoMath.compareDigits(r0.x, r) === 0);
    }

    return {
        createKey: createKey,
        generateKey: generateKey,
        sign: sign,
        verify: verify
    };

};

if (typeof operations !== "undefined") {

    msrcryptoEcdsa.sign = function ( p) {

        var hashName = p.algorithm.hash.name,
            curve = cryptoECC.createCurve(p.keyHandle.algorithm.namedCurve.toUpperCase()),
            hashFunc = msrcryptoHashFunctions[hashName.toLowerCase()],
            digest = hashFunc.computeHash(p.buffer);

        var ecdsa = msrcryptoEcdsa(curve);

        return ecdsa.sign(p.keyData, digest);
    };

    msrcryptoEcdsa.verify = function ( p) {

        var hashName = p.algorithm.hash.name,
            curve = cryptoECC.createCurve(p.keyHandle.algorithm.namedCurve.toUpperCase()),
            hashFunc = msrcryptoHashFunctions[hashName.toLowerCase()],
            digest = hashFunc.computeHash(p.buffer);

        var ecdsa = msrcryptoEcdsa(curve);

        return ecdsa.verify(p.keyData, p.signature, digest);
    };

    msrcryptoEcdsa.generateKey = function (p) {

        var curve = cryptoECC.createCurve(p.algorithm.namedCurve.toUpperCase());

        var ecdsa = msrcryptoEcdsa(curve);

        var keyPairData = ecdsa.generateKey();

        var dtb = cryptoMath.digitsToBytes;

        // Sometimes the result is a byte short because the byte-conversion
        // trims leading zeros. We pad the zeros back on if needed.
        function padTo8BytesIncrement(array) {
            return msrcryptoUtilities.padFront(array, 0, Math.ceil(array.length / 8) * 8);
        }
        var x = padTo8BytesIncrement(dtb(keyPairData.publicKey.x));
        var y = padTo8BytesIncrement(dtb(keyPairData.publicKey.y));
        var d = padTo8BytesIncrement(dtb(keyPairData.privateKey));

        return {
            type: "keyPairGeneration",
            keyPair: {
                publicKey: {
                    keyData: {
                        x: x,
                        y: y
                    },
                    keyHandle: {
                        algorithm: p.algorithm,
                        extractable: p.extractable,
                        keyUsage: null || p.keyUsage,
                        type: "public"
                    }
                },
                privateKey: {
                    keyData: {
                        x: x,
                        y: y,
                        d: d
                    },
                    keyHandle: {
                        algorithm: p.algorithm,
                        extractable: p.extractable,
                        keyUsage: null || p.keyUsage,
                        type: "private"
                    }
                }
            }
        };

    };

    msrcryptoEcdsa.importKey = function (p) {

        var keyObject = msrcryptoJwk.jwkToKey(p.keyData, p.algorithm, ["x", "y", "d", "crv"]);

        // If only private key data 'd' is imported, create x and y
        if (keyObject.d && (!keyObject.x || !keyObject.y)) {

            var curve = msrcryptoEcdsa.curves[p.algorithm.namedCurve]();

            var ecdsa = msrcryptoEcdsa(curve);

            var publicKey = ecdsa.computePublicKey(keyObject.d);

            keyObject.x = publicKey.x;
            keyObject.y = publicKey.y;
        }

        return {
            type: "keyImport",
            keyData: keyObject,
            keyHandle: {
                algorithm: p.algorithm,
                extractable: p.extractable || keyObject.extractable,
                keyUsage: null || p.keyUsage, // IE11 returns null here
                type: (keyObject.d) ? "private" : "public"
            }
        };
    };

    msrcryptoEcdsa.exportKey = function (p) {

        var jsonKeyStringArray = msrcryptoJwk.keyToJwk(p.keyHandle, p.keyData);

        return { type: "keyExport", keyHandle: jsonKeyStringArray };

    };

    operations.register("sign", "ecdsa", msrcryptoEcdsa.sign);

    operations.register("verify", "ecdsa", msrcryptoEcdsa.verify);

    operations.register("generateKey", "ecdsa", msrcryptoEcdsa.generateKey);

    operations.register("importKey", "ecdsa", msrcryptoEcdsa.importKey);

    operations.register("exportKey", "ecdsa", msrcryptoEcdsa.exportKey);

}

var msrcryptoSubtle;

// This code is not used in web worker instance.
if (!runningInWorkerInstance) {

    msrcryptoSubtle = (function() {

// If no native Promise support add ours
if (!window.Promise) {

    window.Promise = function (executor, id) {
        /// <summary>
        /// Creates a new promise.
        /// </summary>
        /// <param name="executor" type="function">A function that takes two parameters: function(resolved, rejected){...}</param>
        /// <returns type="Promise">A new Promise object</returns>

        if (!(this instanceof Promise)) {
            throw new Error("use 'new' keyword with Promise constructor");
        }

        var successResult = null,
            failReason = null,
            thenResolved = [],
            thenRejected = [],
            rejectThenPromise = [],
            resolveThenPromise = [];

        this.then = function (onCompleted, onRejected) {

            var thenFunctionResult;

            // If we already have a result because resolveFunction was synchronous,
            // then just call onCompleted with the result.
            if (successResult) {
                thenFunctionResult = onCompleted(successResult.result);

                if (thenFunctionResult && thenFunctionResult.then) {
                    return thenFunctionResult;
                }

                // Create a new promise; resolve with the result;
                // return the resolved promise.
                return Promise.resolve(thenFunctionResult);
            }

            // If we already have a fail reason from a rejected promise
            if (failReason) {
                thenFunctionResult = onRejected ? onRejected(failReason.result) : failReason.result;

                if (thenFunctionResult && thenFunctionResult.then) {
                    return thenFunctionResult;
                }

                // Create a new promise; reject with the result;
                // return the resolved promise.
                return Promise.resolve(thenFunctionResult);
            }

            // If we do not have a result, store the onCompleted/onRejected functions
            // to call when we do get a result.
            thenResolved.push(onCompleted);
            if (onRejected) {
                thenRejected.push(onRejected);
            }

            // Return a new promise object. This will allow chaining with then/catch().
            return new Promise(function (resolve, reject) {
                resolveThenPromise.push(resolve);
                rejectThenPromise.push(reject);
            });
        }

        this["catch"] = function (onRejected) {

            var catchFunctionResult;

            // If we already have a result because resolveFunction was synchronous,
            // then just call onRejected with the result.
            if (failReason) {
                catchFunctionResult = onRejected(failReason.result);

                if (catchFunctionResult && catchFunctionResult.then) {
                    return catchFunctionResult;
                }

                return Promise.resolve(catchFunctionResult);
            }

            // If we do not have a result, store the onRejected function
            // to call when we do get a result.
            thenRejected.push(onRejected);

            // Return a new promise object. This will allow chaining with then/catch().
            return new Promise(function (resolve, reject) {
                resolveThenPromise.push(resolve);
                rejectThenPromise.push(reject);
            });
        }

        var resolve = function (param) {
            /// <summary>
            /// Called by the executor function when the function has succeeded.
            /// </summary>
            /// <param name="param">A result value that will be passed to the then() function.</param>

            // Call each attached Then function with the result
            for (var i = 0; i < thenResolved.length; i++) {

                var result = thenResolved[i](param);

                // If the result of the then() function is a Promise,
                // set then() to call the chained resolve function.
                if (result && result.then) {
                    result.then(resolveThenPromise[i]);

                } else {

                    // If a then() promise was chained to this promise, call its resolve
                    // function.
                    if (resolveThenPromise[i]) {
                        resolveThenPromise[i](result);
                    }
                }
            }

            // If the onCompleted function has not yet been assigned, store the result.
            successResult = { result: param };

            return;
        }

        function reject(param) {

            // Call each catch function on this promise
            for (var i = 0; i < thenRejected.length; i++) {

                var reason = thenRejected[i](param);

                // If the result of the catch() function is a Promise,
                // set then() to call the chained resolve function.
                if (reason && reason.then) {
                    reason.then(resolveThenPromise[i], rejectThenPromise[i]);

                } else {
                    if (resolveThenPromise[i]) {
                        resolveThenPromise[i](reason);
                    }
                }
            }

            // If the onCompleted function has not yet been assigned, store the result.
            failReason = { result: param };

            return;
        };

        // Call the executor function passing the resolve & reject functions of
        // this instance.

        executor(resolve, reject);

        return;
    }

    //#region static methods

    window.Promise.all = function (promiseArray) {
        /// <summary>
        /// Joins two or more promises and returns only when all the specified promises have completed or been rejected.
        /// </summary>
        /// <param name="promiseArray" type="Array">Array of promises.</param>
        /// <returns type="Promise">Returns a Promise.</returns>

        var results = [],
            resultCount = 0;

        //  Generates a then function for each promise
        function then(index, resolve) {

            return function (result) {

                // We want the results to have the same results index as it was passed in.
                results[index] = result;

                // If all of the promises have returned results, call the resolve function
                // with the results array.
                if (++resultCount == promiseArray.length) {
                    resolve(results);
                }
            }
        }

        // Create a new Promise to return. It's resolve function will call then()
        // on each promise in the arguments list.
        var promiseAll = new Promise(

            function (resolve, reject) {

                for (var i = 0; i < promiseArray.length; i++) {

                    promiseArray[i].then(then(i, resolve));

                    // If a promise fails, return the reason
                    promiseArray[i]["catch"](function (reason) { reject(reason); });
                }
            });

        return promiseAll;
    };

    window.Promise.race = function (promiseArray) {
        /// <summary>
        /// Creates a new promise that will resolve or reject with the same result value as the first promise to resolve or reject among the passed in arguments.
        /// </summary>
        /// <param name="promises" type="Array">Required. One or more promises.</param>
        /// <returns type="Promise">Result of first promise to resolve or fail.</returns>

        var resolved = false;

        //  Generates a then function for each promise
        function then(resolveFunction) {

            return function (result) {

                // When the first promise succeeds/fails, return the answer and ignore the rest.
                if (!resolved) {
                    resolved = true;
                    resolveFunction(result);
                }
            }
        }

        // Create a new Promise to return. It's resolve function will call then()
        // on each promise in the arguments list.
        var promiseRace = new Promise(

            function (resolve, reject) {

                for (var i = 0; i < promiseArray.length; i++) {
                    promiseArray[i].then(then(resolve), then(reject));
                }
            });

        return promiseRace;
    };

    window.Promise.reject = function (rejectReason) {
        /// <summary>
        /// Creates a new rejected promise with a result equal to the passed in argument.
        /// </summary>
        /// <param name="rejectReason" type="">Required. The reason why the promise was rejected.</param>
        /// <returns type=""></returns>

        return new Promise(
            function (resolve, reject) {
                reject(rejectReason);
            });
    };

    window.Promise.resolve = function (resolveResult) {
        /// <summary>
        /// Creates a new resolved promise with a result equal to its argument.
        /// </summary>
        /// <param name="resolveResult" type="">Required. The value returned with the completed promise.</param>
        /// <returns type=""></returns>

        return new Promise(
            function (resolve, reject) {
                resolve(resolveResult);
            });
    };

    //#endregion static methods
}

// This worker is used when webworkers aren't available.
// It will function synchronously but use the same
//   mechanisms that the asynchronous webworkers use.
function syncWorker() {
    var result;

    // PostMessage is how you interact with a worker. You post some data to the worker
    // and it will process it and return it's data to the onmessage function.
    // Since we're really running synchronously, we call the crypto function in
    // PostMessage and wait for the result. Then we call the OnMessage fuction with
    // that result. This will give the same behavior as a web-worker.
    function postMessage(data) {

        // Web-workers will automatically return an error message when an
        // error is thrown within the web worker.
        // When using a sync worker, we'll have to catch thrown errors, so we
        // need a try/catch block here.
        try {
            result = msrcryptoWorker.jsCryptoRunner( { data: data });
        } catch (ex) {
            this.onerror({ data: ex.message, type: "error" });
            return;
        }

        // 'process' operations don't return values, so we don't
        // forward the worker return message.
        if (!data.operationSubType || data.operationSubType !== "process") {
            this.onmessage({ data: result });
        }

    }

    return {
        postMessage: postMessage,
        onmessage: null,
        onerror: null,
        terminate: function () {
            // This is a no-op to be compatible with webworker.
        }
    };
}

/// <dictionary>Obj,oncomplete,onerror</dictionary>

function baseOperation(processResults) {

    var result = null,
        oncompleteCallback = null,
        onerrorCallback = null,
        retObj,
        promise,
        resolveFunc,
        rejectFunc;

    // Create a new promise
    promise = new Promise(
        function (resolve, reject) {
            resolveFunc = resolve;
            rejectFunc = reject;
        });

    // Called when the worker returns a result
    function opDispatchEvent( e) {

        // If the event is an Error call the onError callback
        if (e.type === "error") {

            // If the onerror callback has been set, call it.
            // If the onerror callback has been set, call it.
            if (rejectFunc) {
                rejectFunc.apply(promise, [e.message ? e.message : e]);
            }
            return;
        }

        // Otherwise call the oncomplete callback
        this.result = processResults(e.data);

        // Resolve the promise with the result
        resolveFunc.apply(promise, [this.result]);

        return;
    }

    retObj = {
        dispatchEvent: opDispatchEvent,
        promise : promise,
        result: null
    };

    return retObj;
}

function keyOperation() {

    function processResult(result) {

        // Could be the result of an import, export, generate.
        // Get the keyData and keyHandle out.
        switch (result.type) {

            // KeyImport: save the new key
            case "keyGeneration":
            case "keyImport":
            case "keyDerive":
                keys.add(result.keyHandle, result.keyData);

                return result.keyHandle;

                // KeyExport: return the export data
            case "keyExport":
                return result.keyHandle;

            case "keyPairGeneration":
                keys.add(result.keyPair.publicKey.keyHandle, result.keyPair.publicKey.keyData);
                keys.add(result.keyPair.privateKey.keyHandle, result.keyPair.privateKey.keyData);
                return {
                    publicKey: result.keyPair.publicKey.keyHandle,
                    privateKey: result.keyPair.privateKey.keyHandle
                };

            default:
                throw new Error("Unknown key operation");
        }

        return;
    }

    return baseOperation(processResult);
}

function cryptoOperation(cryptoContext) {

    function processResult(result) {

        // If the browser supports typed-arrays, return an ArrayBuffer like IE11.
        result = toArrayBufferIfSupported(result);

        // A normal array will be returned.
        return result;
    }

    var op = baseOperation(processResult);

    op.process = function (buffer) {
        cryptoContext.operationSubType = "process";
        cryptoContext.buffer = utils.toArray(buffer);
        workerManager.continueJob(this,
            utils.clone(cryptoContext));
    };

    op.finish = function () {
        cryptoContext.operationSubType = "finish";
        cryptoContext.buffer = [];
        workerManager.continueJob(this,
            utils.clone(cryptoContext));
    };

    op.abort = function () {
        workerManager.abortJob(this);
    };

    op.onabort = null;
    op.onprogress = null;

    op.algorithm = cryptoContext.algorithm || null;
    op.key = cryptoContext.keyHandle || null;

    return op;
}

function toArrayBufferIfSupported(dataArray) {

    // If the browser supports typed-arrays, return an ArrayBuffer like IE11.
    if (typedArraySupport && dataArray.pop) {

        // We can't write to an ArrayBuffer directly so we create a Uint8Array
        //   and return it's buffer property.
        return (new Uint8Array(dataArray)).buffer;
    }

    // Do nothing and just return the passed-in array.
    return dataArray;
}

// Storage for the keyData.
// Stored as {keyHandle: keyHandle, keyData: keyData} objects.
var keys = [];
keys.add = function (keyHandle, keyData) {
    keys.push({ keyHandle: keyHandle, keyData: keyData });
};
keys.remove = function (keyHandle) {
    for (var i = 0; i < keys.length; i++) {
        if (keys[i].keyHandle === keyHandle) {
            keys = keys.splice(i, 1);
            return;
        }
    }
};
keys.lookup = function (keyHandle) {
    for (var i = 0; i < keys.length; i++) {
        if (keys[i].keyHandle === keyHandle) {
            return keys[i].keyData;
        }
    }
    return null;
};

// Manages the pool of webworkers and job queue.
// We first try to find an idle webworker and pass it a crypto job.
// If there are no workers or they are all busy, we'll create a new one.
// If we're at our (somewhat arbitrary) limit for workers we'll queue the
//   job until a worker is free.
// When a worker finishes and the queue is empty it will kill itself to
//   free resources.
// However, we will keep a couple idle workers alive for future use.
// In the case webworkers are not supported <IE10 we will run in synchronous
//   mode. Jobs will be executed synchronously as they arrive using a single
//   syncWorker (pretend webworker that just runs synchronously in this same script).
var workerManager = (function () {

    // The max number of webworkers we'll spawn.
    var maxWorkers = 12;

    // The number of idle webworkers we'll allow to live for future use.
    var maxFreeWorkers = 2;

    // Storage for webworker.
    var workerPool = [];

    // Queue for jobs when all workers are busy.
    var jobQueue = [];

    // Each job gets and id.
    var jobId = 0;

    function getFreeWorker() {

        purgeWorkerType(!asyncMode);

        // Get the first non-busy worker
        for (var i = 0; i < workerPool.length; i++) {
            if (!workerPool[i].busy) {
                return workerPool[i];
            }
        }

        return null;
    }

    function purgeWorkerType(webWorker) {
        for (var i = workerPool.length - 1; i >= 0; i -= 1) {
            if (workerPool[i].isWebWorker === webWorker) {
                workerPool[i].terminate();
                workerPool.splice(i, 1);
            }
        }
    }

    function freeWorkerCount() {
        var freeWorkers = 0;
        for (var i = 0; i < workerPool.length; i++) {
            if (!workerPool[i].busy) {
                freeWorkers += 1;
            }
        }
        return freeWorkers;
    }

    function addWorkerToPool(worker) {
        workerPool.push(worker);
    }

    function removeWorkerFromPool(worker) {
        // Find this worker in the array.
        for (var i = 0; i < workerPool.length; i++) {
            if (workerPool[i] === worker) {
                // Kill the webworker.
                worker.terminate();
                // Remove the worker object from the pool.
                workerPool.splice(i, 1);
                return;
            }
        }
    }

    function lookupWorkerByOperation(operation) {
        // Find this worker in the array.
        for (var i = 0; i < workerPool.length; i++) {
            if (workerPool[i].operation === operation) {
                return workerPool[i];
            }
        }
        // Didn't find the worker!?
        return null;
    }

    function queueJob(operation, data) {
        jobQueue.push({ operation: operation, data: data, id: jobId++ });
    }

    function jobCompleted(worker) {

        worker.busy = false;
        worker.operation = null;

        // Check the queue for waiting jobs if in async mode
        if (asyncMode) {
            if (jobQueue.length > 0) {
                var job = jobQueue.shift();
                continueJob(job.operation, job.data);

            } else if (freeWorkerCount() > maxFreeWorkers) {
                removeWorkerFromPool(worker);
            }
        }

    }

    function createNewWorker(operation) {

        // Use a web worker if supported
        //   else use a synchronous worker.
        var worker;

        if (asyncMode) {
            try {
                worker = new Worker(scriptUrl);
                worker.postMessage({ prngSeed: msrcryptoPseudoRandom.getBytes(48) });
                worker.isWebWorker = true;
            } catch (ex) {
                asyncMode = false;
                publicMethods.forceSync = true;
                worker = syncWorker();
                worker.isWebWorker = false;
            }

        } else {
            worker = syncWorker();
            worker.isWebWorker = false;
        }

        // Store the operation object as a property on the worker
        //   so we can know which operation this worker is working for.
        worker.operation = operation;

        worker.busy = false;

        // The worker will call this function when it completes its job.
        worker.onmessage = function ( e) {

            var op = worker.operation;

            // Check if there are queued jobs for this operation
            for (var i = 0; i < jobQueue.length; i++) {
                if (jobQueue[i].operation === worker.operation) {
                    var job = jobQueue[i];
                    jobQueue.splice(i, 1);
                    postMessageToWorker(worker, job.data);
                    return;
                }
            }

            // Send the results to the operation object and it will fire
            // it's onCompleted event.
            if (op) {
                jobCompleted(worker);
                if(e.data && e.data.type !== "process"){
                    op.dispatchEvent(e);
                }
            }
        };

        // If an error occurs within the worker.
        worker.onerror = function ( e) {

            var op = worker.operation;

            jobCompleted(worker);

            // Send the error to the operation object and it will fire
            //   it's onError event.
            op.dispatchEvent(e);

        };

        // Add this new worker to the worker pool.
        addWorkerToPool(worker);

        return worker;
    }

    function abortJob(cryptoOperationObject) {
        var worker = lookupWorkerByOperation(cryptoOperationObject);
        if (worker) {
            removeWorkerFromPool(worker);
        }
    }

    // Creates or reuses a worker and starts it up on work.
    function runJob( operation, data) {

        var worker = null;

        // If the caller adds the "forceSync" property and sets it to true.
        // Then run in synchronous mode even if webworkers are available.
        // This can be turned on or off on the fly.
        asyncMode = webWorkerSupport && !(publicMethods.forceSync);

        // Get the first idle worker.
        worker = getFreeWorker();

        // Queue this job if all workers are busy and we're at our max instances
        if (asyncMode && worker === null && workerPool.length >= maxWorkers) {
            queueJob(operation, data);
            return;
        }

        // No idle workers, we'll have to create a new one.
        if (worker === null) {
            worker = createNewWorker(operation);
        }

        if (worker === null) {
            queueJob(operation, data);
            throw new Error("could not create new worker");
        }

        // Store the operation object as a property on the worker
        //   so we can know which operation this worker is working for.
        worker.operation = operation;

        // Mark this worker as 'busy'. It's about to run a job.
        worker.busy = true;

        // Start the worker
        postMessageToWorker(worker, data);

    }

    function continueJob(/*type(cryptoOperation)*/operation, data) {

        // Lookup the worker that is handling this operation
        var worker = lookupWorkerByOperation(operation);

        if (worker) {
            postMessageToWorker(worker, data);
            return;
        }

        // If we didn't find a worker, this is probably the first
        //  'process' message so we need to start a new worker.
        runJob(operation, data);

    }

    function postMessageToWorker(worker, data) {
        // Start the worker now if using webWorkers
        //   else, defer running until later.
        if (asyncMode) {
            worker.data = data;
            worker.postMessage(data);
        } else {

            var func = (function (postData) {

                return function () {
                    return worker.postMessage(postData);
                }

            })(data);

            var id = setTimeout(func, 0);
        }

        return;
    }

    return {
        runJob: runJob,
        continueJob: continueJob,
        abortJob: abortJob
    };

})();

var utils = msrcryptoUtilities;

function checkOperation(operationType, algorithmName) {
    if (!operations.exists(operationType, algorithmName)) {
        throw new Error("unsupported algorithm");
    }
}

// The list of possible parameters passed to the subtle interface.
var subtleParameters = [
   /* 0 */ { name: "algorithm", type: "Object", required: true },
   /* 1 */ { name: "keyHandle", type: "Object", required: true },
   /* 2 */ { name: "buffer", type: "Array", required: false },
   /* 3 */ { name: "signature", type: "Array", required: true },
   /* 4 */ { name: "format", type: "String", required: true },
   /* 5 */ { name: "keyData", type: "Object", required: true },
   /* 6 */ { name: "extractable", type: "Boolean", required: false },
   /* 7 */ { name: "keyUsage", type: "Array", required: false },
   /* 8 */ { name: "derivedKeyType", type: "Object", required: true },
   /* 9 */ { name: "length", type: "Number", required: false },

   /* 10 */ { name: "extractable", type: "Boolean", required: true },
   /* 11 */ { name: "keyUsage", type: "Array", required: true }
];

// The set of expected parameters passed to each subtle function.
var subtleParametersSets = {
    encrypt: [0, 1, 2],
    decrypt: [0, 1, 2],
    sign: [0, 1, 2],
    verify: [0, 1, 3, 2],
    digest: [0, 2],
    generateKey: [0, 6, 7],
    importKey: [4, 5, 0, 10, 11],
    exportKey: [0, 4, 1, 6, 7],
    deriveKey: [0, 1, 8, 6, 7],
    deriveBits: [0, 1, 9],
    wrapKey: [1, 1, 0],
    unwrapKey: [2, 0, 1, 6, 7]
};

// Looks up the stored key data for a given keyHandle
function lookupKeyData(handle) {
    var data = keys.lookup(handle);

    if (!data) {
        throw new Error("key not found");
    }

    return data;
}

// This function processes each parameter passed by the user. Each parameter
// is compared against an expected parameter. It should be of the expected type.
// Typed-Array parameters are converted to regular Arrays.
function buildParameterCollection(operationName, parameterSet) {

    var parameterCollection = { operationType: operationName },
        operationParameterSet = subtleParametersSets[operationName];

    for (var i = 0; i < operationParameterSet.length; i += 1) {

        var expectedParam = subtleParameters[operationParameterSet[i]];
        var actualParam = parameterSet[i];

        // Verify the required parameters are present.
        if (actualParam==null) {
            if (expectedParam.required) {
                throw new Error(expectedParam.name);
            } else {
                continue;
            }
        }

        // If this parameter is a typed-array convert it to a regular array.
        if (actualParam.subarray) {
            actualParam = utils.toArray(actualParam);
        }

        // If this parameter is an ArrayBuffer convert it to a regular array.
        if (utils.getObjectType(actualParam) == "ArrayBuffer") {
            actualParam = utils.toArray(actualParam);
        }

        // Verify the actual parameter is of the expected type.
        if (msrcryptoUtilities.getObjectType(actualParam) !== expectedParam.type) {
            throw new Error(expectedParam.name);
        }

        // If this parameter is an algorithm object convert it's name to lowercase.
        if (expectedParam.name === "algorithm") {

            actualParam.name = actualParam.name.toLowerCase();

            // If the algorithm has a typed-array IV, convert it to a regular array.
            if (actualParam.iv) {
                actualParam.iv = utils.toArray(actualParam.iv);
            }

            // If the algorithm has a typed-array Salt, convert it to a regular array.
            if (actualParam.salt) {
                actualParam.salt = utils.toArray(actualParam.salt);
            }

            // If the algorithm has a typed-array AdditionalData, convert it to a regular array.
            if (actualParam.additionalData) {
                actualParam.additionalData = utils.toArray(actualParam.additionalData);
            }

            // If this algorithm has a hash property in the form 'hash: hashName'
            // Convert it to hash: {name: hashName} as per the W3C spec.
            if (actualParam.hash && !actualParam.hash.name && msrcryptoUtilities.getObjectType(actualParam.hash) === "String") {
                actualParam.hash = { name: actualParam.hash };
            }
        }

        // KeyWrap has two keyHandle paramters. We add '1' to the second param name
        // to avoid a duplicate name.
        if (parameterCollection.hasOwnProperty(expectedParam.name)) {
            parameterCollection[expectedParam.name + "1"] = actualParam;
        } else {
            parameterCollection[expectedParam.name] = actualParam;
        }
    }

    return parameterCollection;
}

function executeOperation(operationName, parameterSet, keyFunc) {

    var pc = buildParameterCollection(operationName, parameterSet);

    // Verify this type of operation is supported by this library (encrypt, digest, etc...)
    checkOperation(operationName, pc.algorithm.name);

    // Add the key data to the parameter object
    if (pc.keyHandle) {
        pc.keyData = lookupKeyData(pc.keyHandle);
    }

    // Add the key data to the parameter object
    // KeyWrap has two keyHandle parameters - this handles the second key.
    if (pc.keyHandle1) {
        pc.keyData1 = lookupKeyData(pc.keyHandle1);
    }

    // ECDH.DeriveBits passes a public key in the algorithm
    if (pc.algorithm && pc.algorithm.public) {
        pc.additionalKeyData = lookupKeyData(pc.algorithm.public);
    }

    var op = keyFunc ? keyOperation(pc) : cryptoOperation(pc);

    // Run the crypto now if a buffer is supplied
    //   else wait until process() and finish() are called.
    if (keyFunc || pc.buffer || operationName === "deriveBits" || operationName === "wrapKey") {
        workerManager.runJob(op, pc);
    }

    return op.promise;
}

var publicMethods = {

    encrypt: function (algorithm, keyHandle, buffer) {
        /// <signature>
        /// <summary>Encrypt a UInt8Array of data. Encrypt will return an ArrayBuffer if supported, otherwise it will return a regular Array.</summary>
        ///     <param name="algorithm" type="Algorithm"></param>
        ///     <param name="key" type="Key"></param>
        ///     <param name="buffer" type="UInt8Array" optional="true">UInt8Array</param>
        ///     <returns type="ArrayBuffer" />
        /// </signature>
        /// <signature>
        /// <summary>Encrypt an array of bytes. Encrypt will return an ArrayBuffer if supported, otherwise it will return a regular Array.</summary>
        ///     <param name="algorithm" type="Algorithm"></param>
        ///     <param name="key" type="Key"></param>
        ///     <param name="buffer" type="Array" optional="true">An array of bytes (number from 0-255)</param>
        ///     <returns type="Array" />
        /// </signature>
        /// <signature>
        /// <summary>Encrypt an array of bytes. Encrypt will return an ArrayBuffer if supported, otherwise it will return a regular Array.</summary>
        ///     <param name="algorithm" type="Algorithm"></param>
        ///     <param name="key" type="Key"></param>
        ///     <param name="buffer" type="Array" optional="true">A continuous array of bytes (number values from 0-255)</param>
        ///     <returns type="ArrayBuffer" />
        /// </signature>

        return executeOperation("encrypt", arguments, 0);
    },

    decrypt: function (algorithm, keyHandle, buffer) {
        /// <signature>
        ///     <summary>Decrypt a UInt8Array of data.
        ///     Decrypt will return an ArrayBuffer if supported, otherwise it will return an Array of byte values (numbers from 0-255)</summary>
        ///     <param name="algorithm" type="Algorithm"></param>
        ///     <param name="key" type="Key"></param>
        ///     <param name="buffer" type="UInt8Array" optional="true">UInt8Array</param>
        ///     <returns type="CryptoOperation" />
        /// </signature>
        /// <signature>
        ///     <summary>Decrypt an array of byte values. Decrypt will return an ArrayBuffer if supported, otherwise it will return a regular Array.</summary>
        ///     <param name="algorithm" type="Algorithm"></param>
        ///     <param name="key" type="Key"></param>
        ///     <param name="buffer" type="Array" optional="true">An array of bytes values (numbers from 0-255)</param>
        ///     <returns type="CryptoOperation" />
        /// </signature>

        return executeOperation("decrypt", arguments, 0);
    },

    sign: function (algorithm, keyHandle, buffer) {
        /// <signature>
        ///     <summary>Sign a UInt8Array of data.
        ///     Sign will return a signature as an ArrayBuffer if supported, otherwise it will return an Array of byte values (numbers from 0-255)</summary>
        ///     <param name="algorithm" type="Algorithm"></param>
        ///     <param name="key" type="Key"></param>
        ///     <param name="buffer" type="UInt8Array" optional="true">UInt8Array</param>
        ///     <returns type="CryptoOperation" />
        /// </signature>
        /// <signature>
        ///     <summary>Sign an array of byte values. Sign will return an ArrayBuffer if supported, otherwise it will return a regular Array.</summary>
        ///     <param name="algorithm" type="Algorithm"></param>
        ///     <param name="key" type="Key"></param>
        ///     <param name="buffer" type="Array" optional="true">An array of bytes values (numbers from 0-255)</param>
        ///     <returns type="CryptoOperation" />
        /// </signature>

        return executeOperation("sign", arguments, 0);
    },

    verify: function (algorithm, keyHandle, signature, buffer) {
        /// <signature>
        ///     <summary>Verify a signature.</summary>
        ///     <param name="algorithm" type="Algorithm"></param>
        ///     <param name="key" type="Key"></param>
        ///     <param name="signature" type="UInt8Array">UInt8Array</param>
        ///     <param name="buffer" type="UInt8Array" optional="true">UInt8Array</param>
        ///     <returns type="CryptoOperation" />
        /// </signature>
        /// <signature>
        ///     <summary>Verify a signature.</summary>
        ///     <param name="algorithm" type="Algorithm"></param>
        ///     <param name="key" type="Key"></param>
        ///     <param name="signature" type="UInt8Array">UInt8Array</param>
        ///     <param name="buffer" type="Array" optional="true">An array of bytes values (numbers from 0-255)</param>
        ///     <returns type="CryptoOperation" />
        /// </signature>
        /// <signature>
        ///     <summary>Verify a signature.</summary>
        ///     <param name="algorithm" type="Algorithm"></param>
        ///     <param name="key" type="Key"></param>
        ///     <param name="signature" type="Array">An array of bytes values (numbers from 0-255)</param>
        ///     <param name="buffer" type="Array" optional="true">An array of bytes values (numbers from 0-255)</param>
        ///     <returns type="CryptoOperation" />
        /// </signature>
        /// <signature>
        ///     <summary>Verify a signature.</summary>
        ///     <param name="algorithm" type="Algorithm"></param>
        ///     <param name="key" type="Key"></param>
        ///     <param name="signature" type="Array">An array of bytes values (numbers from 0-255)</param>
        ///     <param name="buffer" type="UInt8Array" optional="true">UInt8Array</param>
        ///     <returns type="CryptoOperation" />
        /// </signature>

        return executeOperation("verify", arguments, 0);
    },

    digest: function (algorithm, buffer) {
        /// <signature>
        ///     <summary>Digest data using a specified cryptographic hash algorithm</summary>
        ///     <param name="algorithm" type="Algorithm"></param>
        ///     <param name="buffer" type="UInt8Array" optional="true">UInt8Array</param>
        ///     <returns type="CryptoOperation" />
        /// </signature>
        /// <signature>
        ///     <summary>Digest data using a specified cryptographic hash algorithm</summary>
        ///     <param name="algorithm" type="Algorithm"></param>
        ///     <param name="buffer" type="Array" optional="true">An array of bytes values (numbers from 0-255)</param>
        ///     <returns type="CryptoOperation" />
        /// </signature>
        return executeOperation("digest", arguments, 0);
    },

    generateKey: function (algorithm, extractable, keyUsage) {
        /// <signature>
        ///     <summary>Generate a new key for use with the algorithm specified by the algorithm parameter</summary>
        ///     <param name="algorithm" type="Algorithm"></param>
        ///     <param name="extractable" type="Boolean" optional="true"></param>
        ///     <param name="keyUsage" type="Array" optional="true"></param>
        ///     <returns type="KeyOperation" />
        /// </signature>

        return executeOperation("generateKey", arguments, 1);
    },

    deriveKey: function (algorithm, baseKey, derivedKeyType, extractable, keyUsage) {
        /// <signature>
        ///     <summary>Generate a key for the specified derivedKeyType, using the specified cryptographic key derivation algorithm with the given baseKey as input.</summary>
        ///     <param name="algorithm" type="Algorithm"></param>
        ///     <param name="baseKey" type="Key"></param>
        ///     <param name="deriveKeyType" type="Algorithm"></param>
        ///     <param name="extractable" type="Boolean" optional="true"></param>
        ///     <param name="keyUsage" type="Array" optional="true"></param>
        ///     <returns type="KeyOperation" />
        /// </signature>

        return executeOperation("deriveKey", arguments, 1);
    },

    deriveBits: function (algorithm, baseKey, length) {
        /// <signature>
        ///     <summary>Generate an array of bytes from a given baseKey as input.</summary>
        ///     <param name="algorithm" type="Algorithm"></param>
        ///     <param name="baseKey" type="Key"></param>
        ///     <param name="length" type="Number">Number of bytes to return.</param>
        ///     <returns type="CryptoOperation" />
        /// </signature>

        return executeOperation("deriveBits", arguments, 0);
    },

    importKey: function (format, keyData, algorithm, extractable, keyUsage) {
        /// <signature>
        ///     <summary>Constructs a new Key object using the key data specified by the keyData parameter.</summary>
        ///     <param name="format" type="String"></param>
        ///     <param name="keyData" type="Object">An object representing a key in jwk format.</param>
        ///     <param name="algorithm" type="Algorithm"></param>
        ///     <param name="extractable" type="Boolean" optional="true"></param>
        ///     <param name="keyUsage" type="Array" optional="true"></param>
        ///     <returns type="KeyOperation" />
        /// </signature>

        return executeOperation("importKey", arguments, 1);
    },

    exportKey: function (format, keyHandle) {
        /// <signature>
        ///     <summary>Exports the given key material of the Key object as specified by the key parameter.</summary>
        ///     <param name="format" type="String"></param>
        ///     <param name="key" type="Key"></param>
        ///     <returns type="KeyOperation" />
        /// </signature>

        // Export is one of the few calls where the caller does not supply an algorithm
        // since it's already a property of the key to be exported.
        // So, we're pulling it out of the key and adding it to the parameter set since
        // it is used as a switch to route the parameters to the right function.
        // Now we don't have to treat this as a special case in the underlying code.
        return executeOperation("exportKey", [keyHandle.algorithm, format, keyHandle], 1);
    },

    wrapKey: function (format, key, wrappingKey, wrappingKeyAlgorithm) {
        /// <signature>
        ///     <summary>Returns a KeyOperation object which will asynchronously return an array containing the key material of key, encrypted with keyEncryptionKey using the specified keyWrappingAlgorithm.</summary>
        ///     <param name="format" type="String"></param>
        ///     <param name="key" type="Key"></param>
        ///     <param name="wrappingKey" type="Key"></param>
        ///     <param name="wrappingKeyAlgorithm" type="Algorithm"></param>
        ///     <returns type="KeyOperation" />
        /// </signature>

        return executeOperation("wrapKey", arguments, 0);
    },

    unwrapKey: function (format, unwrappingKey, unwrapAlgorithm, unwrappedKeyAlgorithm, extractable, keyUsage) {
        /// <signature>
        ///     <summary>Construct a Key object from encrypted key material.</summary>
        ///     <param name="format" type="String"></param>
        ///     <param name="unwrappingKey" type="Array">An array of bytes values (numbers from 0-255)</param>
        ///     <param name="unwrapAlgorithm" type="Algorithm"></param>
        ///     <param name="keyEncryptionKey" type="Key"></param>
        ///     <param name="extractableunwrappedKeyAlgorithm type="Boolean" optional="true"></param>
        ///     <param name="keyUsage" type="Array" optional="true"></param>
        ///     <returns type="KeyOperation" />
        /// </signature>
        /// <signature>
        ///     <summary>Construct a Key object from encrypted key material.</summary>
        ///     <param name="format" type="String"></param>
        ///     <param name="unwrappingKey" type="UInt8Array"></param>
        ///     <param name="unwrapAlgorithm" type="Algorithm"></param>
        ///     <param name="unwrappedKeyAlgorithm" type="Key"></param>
        ///     <param name="extractable" type="Boolean" optional="true"></param>
        ///     <param name="keyUsage" type="Array" optional="true"></param>
        ///     <returns type="KeyOperation" />
        /// </signature>

        return executeOperation("unwrapKey", arguments, 1);
    }

};

return publicMethods;

})();

}

var msrcryptoWrapKey = (function () {

    var utils = msrcryptoUtilities;

    function wrapKey(params) {

        var rsaObj = msrcryptoRsa(
            params.keyData1,
            params.keyHandle1.algorithm.name,
            msrcryptoHashFunctions["sha-1"]);

        var tagLength = 128;

        var keyToWrapJwk = msrcryptoJwk.keyToJwkOld(params.keyHandle, params.keyData);

        var jweHeader = {
            "alg": params.keyHandle1.algorithm.name.toUpperCase(),
            "enc": "A128GCM"
        };

        var encodedJweHeader =
            utils.toBase64(JSON.stringify(jweHeader), true);

        var cmk = msrcryptoPseudoRandom.getBytes(32);

        var jweEncryptedKey = rsaObj.encrypt(cmk);

        var encodedJweEncryptedKey = utils.toBase64(jweEncryptedKey, true);

        var jweIv = msrcryptoPseudoRandom.getBytes(12);

        var encodedJweIv = utils.toBase64(jweIv, true);

        var additionalData = encodedJweHeader.concat(".", encodedJweEncryptedKey, ".", encodedJweIv);

        var gcm = msrcryptoGcm(msrcryptoBlockCipher.aes(cmk));
        gcm.init(jweIv, utils.stringToBytes(additionalData), tagLength);

        var ciphertextPlusTag = gcm.encrypt(keyToWrapJwk);

        var tag = ciphertextPlusTag.slice(-(tagLength / 8));

        var encodedIntegrityValue = utils.toBase64(tag, true);

        var encodedCiphertext =
            utils.toBase64(ciphertextPlusTag.slice(0, ciphertextPlusTag.length - tag.length), true);

        var jwe = {

            recipients: [{
                header: encodedJweHeader,
                encrypted_key: encodedJweEncryptedKey,
                integrity_value: encodedIntegrityValue
            }
            ],
            initialization_vector: encodedJweIv,
            ciphertext: encodedCiphertext

        }

        return utils.stringToBytes(JSON.stringify(jwe));

    }

    function unwrapKey(params) {

        var b64Tobytes = utils.base64ToBytes;

        var keyDataJwk =
            JSON.parse(String.fromCharCode.apply(null, params.buffer));

        var header = utils.base64ToString(keyDataJwk.recipients[0].header);

        var encrypted_key =
            b64Tobytes(keyDataJwk.recipients[0].encrypted_key);

        var integrity_value =
            b64Tobytes(keyDataJwk.recipients[0].integrity_value);

        var initialization_vector =
            b64Tobytes(keyDataJwk.initialization_vector);

        var ciphertext =
            b64Tobytes(keyDataJwk.ciphertext);

        var hashFunc = msrcryptoHashFunctions["sha-1"];
        var rsaObj = msrcryptoRsa(params.keyData, params.keyHandle.algorithm.name, hashFunc);
        var inKey = rsaObj.decrypt(encrypted_key);

        var additionalData =
            keyDataJwk.recipients[0].header.concat(".", keyDataJwk.recipients[0].encrypted_key, ".", keyDataJwk.initialization_vector);

        var gcm = msrcryptoGcm(msrcryptoBlockCipher.aes(inKey));
        gcm.init(initialization_vector, utils.stringToBytes(additionalData), 128);

        var result = gcm.decrypt(ciphertext, integrity_value);

        var keyObject = msrcryptoJwk.jwkToKey(result, params.algorithm, ["k"]);

        return {
            type: "keyImport",
            keyData: keyObject.k,
            keyHandle: {
                algorithm: { name: params.algorithm.name },
                extractable: params.extractable || keyObject.extractable,
                keyUsage: params.keyUsage,
                type: "secret"
            }
        }
    }

    return {
        wrapKey: wrapKey,
        unwrapKey: unwrapKey

    };

})();

if (typeof operations !== "undefined") {
    operations.register("wrapKey", "aes-gcm", msrcryptoWrapKey.wrapKey);
    operations.register("unwrapKey", "aes-cbc", msrcryptoWrapKey.unwrapKey);
}

var publicMethods = {

    /// <field type = 'Object' static="false">Microsoft Research Javascript Crypto Library Subtle interface.</field>
    subtle: msrcryptoSubtle,

    getRandomValues: function(array) {
        /// <signature>
        ///     <summary>Places cryptographically random values into the given array.</summary>
        ///     <param name="array" type="Array"></param>
        ///     <returns type="Array" />
        /// </signature>
        /// <signature>
        ///     <summary>Places cryptographically random values into the given array.</summary>
        ///     <param name="array" type="ArrayBufferView"></param>
        ///     <returns type="ArrayBufferView">Returns ArrayBufferView if supported.</returns>
        /// </signature>

        var i;
        var randomValues = msrcryptoPseudoRandom.getBytes(array.length);
        for (i = 0; i < array.length; i+=1) {
            array[i] = randomValues[i];
        }
        return array;
    },

    initPrng: function (entropyData) {
        /// <signature>
        ///     <summary>Add entropy to the PRNG.</summary>
        ///     <param name="entropyData" type="Array">Entropy input to seed or reseed the PRNG.</param>
        /// </signature>

        var entropyDataType = Object.prototype.toString.call(entropyData);

        if (entropyDataType !== "[object Array]" && entropyDataType !== "[object Uint8Array]") {
            throw new Error("entropyData must be a Array or Uint8Array");
        }

        // Mix the user-provided entropy into the entropy pool - only in the main thread.
        entropyPool && entropyPool.reseed(entropyData);

        // Reseed the PRNG that was initialized below
        msrcryptoPseudoRandom.reseed(entropyPool.read(48));
        fprngEntropyProvided = true;
    },

    toBase64: function (data, toBase64Url) {
        /// <signature>
        ///     <summary>Convert string or array data to Base64.</summary>
        ///     <param name="data" type="Array">Byte values (numbers 0-255)</param>
        ///     <param name="toBase64Url" type="Boolean" optional="true">Return Base64Url encoding (this is different from Base64 encoding.)</param>
        ///     <returns type="Array" />
        /// </signature>
        /// <signature>
        ///     <summary>Convert string or array data to Base64.</summary>
        ///     <param name="data" type="String"></param>
        ///     <param name="toBase64Url" type="Boolean" optional="true">Return Base64Url encoding (this is different from Base64 encoding.)</param>
        ///     <returns type="Array" />
        /// </signature>

        return  msrcryptoUtilities.toBase64(data, false);
    },

    base64ToString: function (base64String) {
        /// <signature>
        ///     <summary>Decode a Base64 encoded string to a plain string.</summary>
        ///     <param name="base64String" type="String">Base64 encoded string.</param>
        ///     <returns type="String" />
        /// </signature>
        return msrcryptoUtilities.base64ToString(base64String);
    },

    /// <field type = 'String'>URL of the this msrCrypto script.</field>
    url : scriptUrl

};

// Expose the math library if present
if (typeof cryptoMath !== "undefined") {
    publicMethods.cryptoMath = cryptoMath;
}

if (typeof testInterface !== "undefined") {
    publicMethods.testInterface = testInterface;
}

// Initialize the main entropy pool instance on the main thread, only.
// I want only the main thread to create and manage the central entropy pool.
// All workers would have their own PRNG instance initialized by injected entropy from the main thread.
var entropyPool;
if (!runningInWorkerInstance) {
    entropyPool = entropyPool || new MsrcryptoEntropy();

    // Initialize the entropy pool in the main thread.
    // There is only one entropy pool.
    entropyPool.init();
    var localEntropy = entropyPool.read(48);            // 48 is from SP800-90A; could be longer
    msrcryptoPseudoRandom.init(localEntropy);
}

function standardizeAlgoName(name) {
    var upper = name.toUpperCase();
    if (upper === 'RSASSA-PKCS1-V1_5') return 'RSASSA-PKCS1-v1_5';
    return upper;
}

function standardizeAlgo(algo) {
    algo.name = algo.name && standardizeAlgoName(algo.name);
    if (algo.hash) algo.hash.name = algo.hash.name && algo.hash.name.toUpperCase();
}

function b64u2bin(b64u) {
    return atob(b64u
        .replace(/-/g, '+')
        .replace(/_/g, '/'));
}

function bin2Uint8(bin) {
    var uint8Array = typeof Uint8Array === 'undefined' ? [] : new Uint8Array(bin.length);
    for (var s = 0, sl = bin.length; s < sl; s++) {
        uint8Array[s] = bin.charCodeAt(s);
    }
    return uint8Array;
}

var algMap = {
    HMAC: {
        'SHA-256': 'HS256',
        'SHA-384': 'HS384',
        'SHA-512': 'HS512'
    },
    'AES-GCM': {
        128: 'A128GCM',
        192: 'A192GCM',
        256: 'A256GCM'
    },
    'AES-CBC': {
        128: 'A128CBC',
        192: 'A192CBC',
        256: 'A256CBC'
    }
};

function getAlg(algorithm, key) {
    var key1 = algorithm.name;
    if (!key1) key1 = algorithm;
    var key2 = algorithm.length || (key.length * 8);
    if (key1 === 'HMAC') {
        key2 = algorithm.hash.name;
    }
    var alg = algMap[key1] && algMap[key1][key2];
    if (!alg) {
        console.log(algorithm, key);
        throw new Error('Could not find an appropriate alg');
    }
    return alg;
}

function toArrayBufferIfSupported(dataArray) {

    // If the browser supports typed-arrays, return an ArrayBuffer like IE11.
    if (typedArraySupport && dataArray.pop) {

        // We can't write to an ArrayBuffer directly so we create a Uint8Array
        //   and return it's buffer property.
        return (new Uint8Array(dataArray)).buffer;
    }

    // Do nothing and just return the passed-in array.
    return dataArray;
}

if (publicMethods && publicMethods.subtle) {
    var originalImportKey = publicMethods.subtle.importKey;
    publicMethods.subtle.importKey = function importKey() {
        var importType = arguments[0];
        var key = arguments[1];
        var algorithm = arguments[2];
        if (importType === 'raw') {
            key = msrcryptoUtilities.toArray(key);
            key = arguments[1] = {
                kty: 'oct',
                k: msrcryptoUtilities.toBase64(key),
                alg: getAlg(algorithm, key),
                ext: true
            }
        }
        return originalImportKey.apply(this, arguments)
        .then(function(res) {
            standardizeAlgo(res.algorithm);
            switch(res.type) {
                case 'secret':
                    res.usages = ['sign', 'verify'];
                    break;
                case 'private':
                    res.usages = ['sign'];
                    break;
                case 'public':
                    res.usages = ['verify'];
                    break;
            }
            if (key.kty === 'RSA') {
                res.algorithm.modulusLength = b64u2bin(key.n).length * 8;
                res.algorithm.publicExponent = bin2Uint8(b64u2bin(key.e));
            }
            return res;
        });
    }

    var originalGenerateKey = publicMethods.subtle.generateKey;
    publicMethods.subtle.generateKey = function generateKey() {
        return originalGenerateKey.apply(this, arguments)
        .then(function(res) {
            if (res.publicKey) {
                res.publicKey.usages = ['verify'];
                standardizeAlgo(res.publicKey.algorithm);
                res.privateKey.usages = ['sign'];
                standardizeAlgo(res.privateKey.algorithm);
            } else {
                res.usages = ['sign', 'verify'];
                standardizeAlgo(res.algorithm);
            }
            return res;
        });
    }

    var originalExportKey = publicMethods.subtle.exportKey;
    publicMethods.subtle.exportKey = function exportKey() {
        var exportType = arguments[0];
        return originalExportKey.apply(this, arguments)
        .then(function(res) {
            if (res.kty === 'RSA' || res.kty === 'EC') {
                if (res.d) {
                    res.key_ops = ['sign'];
                } else {
                    res.key_ops = ['verify'];
                }
            }
            if (exportType === 'raw') {
                res = toArrayBufferIfSupported(msrcryptoUtilities.base64ToBytes(res.k));
            }
            return res;
        });
    }
}

return publicMethods;

})();

export const subtle = msrCrypto.subtle;
export default msrCrypto;

Выполнить команду


Для локальной разработки. Не используйте в интернете!