diff --git a/Dockerfile b/Dockerfile
index a072a900e07bee18ab81b1e4794745777199af8c..d088fec85bfda919e93a4b1811f27ac3c38fb0f8 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,4 +1,4 @@
-FROM registry.vereign.com/docker/vcl-build-base:buster as builder
+FROM registry.vereign.com/docker/vcl-build-base:golang1.14.1 as builder
 
 ARG GITLAB_LOGIN
 ARG GITLAB_PASSWORD
@@ -11,5 +11,7 @@ RUN git config --global url."https://$GITLAB_LOGIN:$GITLAB_PASSWORD@code.vereign
 
 FROM registry.vereign.com/docker/go-runtime:master
 COPY --from=builder /go/src/code.vereign.com/code/vcl/javascript/dist /srv/dist
+COPY --from=builder /go/src/code.vereign.com/code/vcl/Gopkg.lock /srv/dist/
+
 ENTRYPOINT ["/bin/cp","-a","/srv/dist/.","/srv/target"]
 
diff --git a/javascript/src/constants/secrets.js b/javascript/src/constants/secrets.js
new file mode 100644
index 0000000000000000000000000000000000000000..41b9ef5ee76b93cffcc5141a9534e013ff065dbb
--- /dev/null
+++ b/javascript/src/constants/secrets.js
@@ -0,0 +1,2 @@
+export const RECOMMENDED_TRUSTEES = 3;
+export const THRESHOLD = 2;
diff --git a/javascript/src/iframe/viamapi-iframe.js b/javascript/src/iframe/viamapi-iframe.js
index 06421ddc10d4f098559a4e891b9a51e509d1e9f6..96c943b8024053af72e7ae102a1bf42c7cb5a302 100644
--- a/javascript/src/iframe/viamapi-iframe.js
+++ b/javascript/src/iframe/viamapi-iframe.js
@@ -41,6 +41,12 @@ import {
   STATUS_USER_BLOCKED
 } from "../constants/statuses";
 import generateQrCode from "../utilities/generateQrCode";
+import {
+  generateRecoveryKey,
+  getRecoveryKeyShares,
+  checkRecoveryKeyCombine,
+  encryptShare
+} from "../utilities/secrets";
 
 const penpalMethods = require("../../temp/penpal-methods").default;
 const WopiAPI = require("./wopiapi-iframe");
@@ -271,14 +277,13 @@ window.lastTimeGetProfile = 0;
 let iframeParent = null;
 
 const handleIdentityLogin = (identity, uuid, token) => {
-  const { loadedIdentities, viamApi } = window;
+  const { viamApi } = window;
   const { publicKey } = identity.authentication;
-
   viamApi.setSessionData(uuid, token);
   localStorage.setItem("uuid", uuid);
   localStorage.setItem("token", token);
   localStorage.setItem("authenticatedIdentity", publicKey);
-  window.currentlyAuthenticatedIdentity = loadedIdentities[publicKey];
+  window.currentlyAuthenticatedIdentity = identity;
   window.lastTimeGetProfile = 0;
   setKeyForUUID(uuid, publicKey);
 };
@@ -348,6 +353,7 @@ async function executeRestfulFunction(type, that, fn, config, ...args) {
     null,
     "previousaddeddevice"
   );
+
   if (loginResponse.data.code !== "200") {
     return loginResponse.data;
   }
@@ -434,7 +440,7 @@ function getCertificateForPassport(passportUUID, internal) {
     const passportIdentity = window.currentlyAuthenticatedIdentity;
     const passport = passportIdentity.getPassport(passportUUID);
     if (passport === undefined || passport === null) {
-      createPassportCertificate(passportUUID).then(function(keys) {
+      createPassportCertificate(passportUUID).then(function (keys) {
         const cryptoData = new CryptoData();
         cryptoData.setPublicKey(keys["publicKeyPEM"]);
         cryptoData.setPrivateKey(keys["privateKeyPEM"]);
@@ -552,7 +558,7 @@ const connection = Penpal.connectToParent({
     ...penpalMethods,
     createIdentity(pinCode) {
       return new Penpal.Promise(result => {
-        createPassportCertificate(makeid()).then(function(keys) {
+        createPassportCertificate(makeid()).then(function (keys) {
           const newIdentity = new Identity();
           const cryptoData = new CryptoData();
           cryptoData.setPublicKey(keys["publicKeyPEM"]);
@@ -562,6 +568,10 @@ const connection = Penpal.connectToParent({
           newIdentity.setPinCode(pinCode);
 
           window.currentlyLoadedIdentity = newIdentity;
+          localStorage.setItem(
+            "currentlyLoadedIdentity",
+            JSON.stringify(newIdentity)
+          );
           const { publicKey, x509Certificate } = newIdentity.authentication;
 
           window.loadedIdentities[publicKey] = newIdentity;
@@ -724,10 +734,7 @@ const connection = Penpal.connectToParent({
         });
       });
     },
-    finalizeEmployeeRegistration: async (
-      identity,
-      identifier
-    ) => {
+    finalizeEmployeeRegistration: async (identity, identifier) => {
       viamApi.setIdentity(identity.authentication.publicKey);
       return executeRestfulFunction(
         "public",
@@ -812,16 +819,9 @@ const connection = Penpal.connectToParent({
       const responseToClient = Object.assign({}, identityLoginResponse);
 
       if (code === "200") {
-        if (
-          mode === LOGIN_MODES.SMS ||
-          mode === LOGIN_MODES.PREVIOUSLY_ADDED_DEVICE
-        ) {
+        if (mode === LOGIN_MODES.PREVIOUSLY_ADDED_DEVICE) {
           handleIdentityLogin(loginIdentity, data.Uuid, data.Session);
           await getProfileData(loginIdentity);
-
-          if (mode === LOGIN_MODES.SMS) {
-            await setIdentityInLocalStorage(loginIdentity);
-          }
         } else if (mode === LOGIN_MODES.NEW_DEVICE) {
           const dataUrl = await generateQrCode(
             `${data.ActionID},${data.QrCode}`
@@ -961,7 +961,7 @@ const connection = Penpal.connectToParent({
         };
       }
     },
-    identityRestoreAccess(restoreAccessIdentity, identificator) {
+    identityRestoreAccess(restoreAccessIdentity, identificator, restoreType) {
       return new Penpal.Promise(result => {
         viamApi.setSessionData("", "");
         viamApi.setIdentity(restoreAccessIdentity.authentication.publicKey);
@@ -971,12 +971,163 @@ const connection = Penpal.connectToParent({
           viamApi,
           viamApi.identityRestoreAccess,
           null,
-          identificator
+          identificator,
+          restoreType
         ).then(executeResult => {
           result(executeResult);
         });
       });
     },
+    identityInitiateSocialRecovery: async accessToken => {
+      const response = await executeRestfulFunction(
+        "public",
+        viamApi,
+        viamApi.identityInitiateSocialRecovery,
+        null,
+        accessToken
+      );
+
+      return response;
+    },
+    contactsCheckAccountRecoveryStatus: async () => {
+      const currentlyLoadedIdentity = localStorage.getItem(
+        "currentlyLoadedIdentity"
+      );
+      const parsedIdentity = JSON.parse(currentlyLoadedIdentity);
+      window.currentlyLoadedIdentity = parsedIdentity;
+      const { publicKey } = parsedIdentity.authentication;
+      window.loadedIdentities[publicKey] = parsedIdentity;
+      window.viamAnonymousApi.setIdentity(publicKey);
+      window.viamApi.setSessionData("", "");
+      window.viamApi.setIdentity(publicKey);
+
+      const timeout = ms => new Promise(resolve => setTimeout(resolve, ms));
+
+      async function checkAccountRecoveryStatus() {
+        const response = await executeRestfulFunction(
+          "public",
+          viamApi,
+          viamApi.contactsCheckAccountRecoveryStatus,
+          null
+        );
+
+        if (response.data === 0) {
+          await timeout(1000);
+          await checkAccountRecoveryStatus();
+          return;
+        }
+
+        const deviceHash = await createDeviceHash(publicKey);
+        window.viamApi.setDeviceHash(deviceHash);
+
+        const identityLoginResponse = await executeRestfulFunction(
+          "public",
+          window.viamApi,
+          window.viamApi.identityLogin,
+          null,
+          "previousaddeddevice"
+        );
+
+        const { code, data } = identityLoginResponse;
+        if (code === "200") {
+          await setIdentityInLocalStorage(parsedIdentity);
+          handleIdentityLogin(parsedIdentity, data.Uuid, data.Session);
+          await getProfileData(parsedIdentity);
+          localStorage.removeItem("currentlyLoadedIdentity");
+        }
+      }
+
+      await checkAccountRecoveryStatus();
+    },
+    contactsGetTrusteeContactsPublicKeys: async () => {
+      try {
+        const response = await executeRestfulFunction(
+          "private",
+          window.viamApi,
+          window.viamApi.contactsGetTrusteeContactsPublicKeys,
+          null
+        );
+
+        if (response.code !== "200") {
+          return response;
+        }
+
+        const responseData = response.data;
+        const trusteesDevices = Object.values(responseData);
+
+        /** Check if there are new trustees without added secret part */
+        const hasNewTrustees = trusteesDevices.some(device => {
+          const deviceData = Object.values(device);
+          return deviceData.some(data => data.hasShamir === "0");
+        });
+
+        if (!hasNewTrustees) {
+          return response;
+        }
+
+        // Generate and split recovery key
+        const trusteesUuids = Object.keys(responseData);
+        const trusteesToDevices = Object.entries(responseData);
+        const sharesNumber = trusteesUuids.length;
+        const recoveryKey = generateRecoveryKey();
+        let recoveryKeyShares = [recoveryKey];
+        // Split the secret when sharesNumber is more than 1 because VereignPublicKey is always returned
+        if (sharesNumber > 1) {
+          recoveryKeyShares = getRecoveryKeyShares(recoveryKey, sharesNumber);
+          const sanityCheckResponse = checkRecoveryKeyCombine(
+            recoveryKey,
+            recoveryKeyShares
+          );
+
+          if (sanityCheckResponse.code !== "200") {
+            return sanityCheckResponse;
+          }
+        }
+
+        // Encrypt each share with every publicKey of each contact device
+        const shamirPartsList = await Promise.all(
+          trusteesToDevices.map(async ([contactUuid, device], index) => {
+            const deviceIdsToPublicKeys = Object.entries(device);
+            // Encrypt secret shares in parallel
+            const deviceIdsToEncryptedPartsList = await Promise.all(
+              deviceIdsToPublicKeys.map(async ([deviceId, { content }]) => {
+                const encryptedShare = await encryptShare(
+                  recoveryKeyShares[index],
+                  content
+                );
+
+                return [deviceId, encryptedShare];
+              })
+            );
+            // Turn deviceIdsToEncryptedPartsList array to object
+            const deviceIdsToEncryptedParts = Object.fromEntries(
+              deviceIdsToEncryptedPartsList
+            );
+
+            return [contactUuid, deviceIdsToEncryptedParts];
+          })
+        );
+        // Turn shamirPartsList array to object
+        const shamirParts = Object.fromEntries(shamirPartsList);
+
+        // Save Shamir parts to database
+        const saveShamirPartsResponse = await executeRestfulFunction(
+          "private",
+          window.viamApi,
+          window.viamApi.contactsSaveShamirParts,
+          null,
+          shamirParts
+        );
+
+        if (saveShamirPartsResponse.code !== "200") {
+          return saveShamirPartsResponse;
+        }
+
+        return response;
+      } catch (error) {
+        return encodeResponse("400", "", error.message);
+      }
+    },
     parseSMIME,
     getCurrentlyLoggedInUUID() {
       return new Penpal.Promise(result => {
@@ -1074,7 +1225,7 @@ const connection = Penpal.connectToParent({
                 emailArg,
                 passportPrivateKey,
                 passportCertificate
-              ).then(function(keys) {
+              ).then(function (keys) {
                 const publicKeyOneTime = keys["publicKeyPEM"];
                 const privateKeyOneTime = keys["privateKeyPEM"];
                 const certificateOneTime = keys["certificatePEM"];
@@ -1417,10 +1568,7 @@ const connection = Penpal.connectToParent({
         vCardImageClaimValue = vCardClaimResponse.data;
       }
 
-      if (
-        vCardImageClaimValue &&
-        "state" in vCardImageClaimValue
-      ) {
+      if (vCardImageClaimValue && "state" in vCardImageClaimValue) {
         return encodeResponse("200", vCardImageClaimValue.state, "OK");
       }
 
@@ -1480,14 +1628,13 @@ message SignatureData {
         return encodeResponse("400", "", "Identity not authenticated");
       }
 
-
       // Get vCard and QR Code Coordinates
 
       let vCardImageData;
       let vCardImageClaimValue;
 
       let qrCodeImageData;
-      let qrCodeCoordinates = {fromL: -1, fromR: -1, toL: -1, toR: -1};
+      let qrCodeCoordinates = { fromL: -1, fromR: -1, toL: -1, toR: -1 };
 
       if (signatureData) {
         const vCardImageClaimName = "vCardImage";
@@ -1673,7 +1820,7 @@ message SignatureData {
       let vCardImageClaimValue;
 
       let qrCodeImageData;
-      let qrCodeCoordinates = {fromL: -1, fromR: -1, toL: -1, toR: -1};
+      let qrCodeCoordinates = { fromL: -1, fromR: -1, toL: -1, toR: -1 };
 
       const vCardImageClaimName = "vCardImage";
       const defaultTagName = "notag";
@@ -1809,7 +1956,6 @@ message SignatureData {
 
       passportChain.reverse();
 
-
       const signVCardResponse = await executeRestfulFunction(
         "private",
         window.viamApi,
@@ -1927,7 +2073,7 @@ message SignatureData {
 
       return encodeResponse("200", response.data, "Document created");
     },
-    getVcardWithQrCode: async (passportUUID, QRCodeContent = null) =>{
+    getVcardWithQrCode: async (passportUUID, QRCodeContent = null) => {
       //TODO: IMPLEMENT QR CODE backend method needed
       const authenticationPublicKey = localStorage.getItem(
         "authenticatedIdentity"
@@ -1995,7 +2141,7 @@ message SignatureData {
           );
         }
       }
-      return  encodeResponse("200",vCardImageData, 'vCard got');
+      return encodeResponse("200", vCardImageData, "vCard got");
     },
     documentPutDocument: async (
       passportUUID,
@@ -2413,7 +2559,7 @@ connection.promise.then(parent => {
   let previousLocalStorageToken;
   let previousLocalStorageIdentity;
 
-  setInterval(async function() {
+  setInterval(async function () {
     if (window.currentlyAuthenticatedIdentity) {
       const { authentication } = window.currentlyAuthenticatedIdentity;
       const pinCode = getPincode(authentication.publicKey);
@@ -2458,6 +2604,8 @@ connection.promise.then(parent => {
         identityAuthenticatedEvent = false;
         window.currentlyLoadedIdentity = null;
       }
+
+      localStorage.removeItem("currentlyLoadedIdentity");
     }
 
     if (window.currentlyLoadedIdentity) {
diff --git a/javascript/src/lib/secrets.js b/javascript/src/lib/secrets.js
new file mode 100644
index 0000000000000000000000000000000000000000..9da60b9782fa0494d2827ae9c0b2193994a7d70d
--- /dev/null
+++ b/javascript/src/lib/secrets.js
@@ -0,0 +1,1043 @@
+// @preserve author Alexander Stetsyuk
+// @preserve author Glenn Rempe <glenn@rempe.us>
+// @license MIT
+
+/*jslint passfail: false, bitwise: true, nomen: true, plusplus: true, todo: false, maxerr: 1000 */
+/*global define, require, module, exports, window, Uint32Array */
+
+// eslint : http://eslint.org/docs/configuring/
+/*eslint-env node, browser, jasmine */
+/*eslint no-underscore-dangle:0 */
+
+// UMD (Universal Module Definition)
+// Uses Node, AMD or browser globals to create a module. This module creates
+// a global even when AMD is used. This is useful if you have some scripts
+// that are loaded by an AMD loader, but they still want access to globals.
+// See : https://github.com/umdjs/umd
+// See : https://github.com/umdjs/umd/blob/master/returnExportsGlobal.js
+//
+(function(root, factory) {
+  "use strict";
+
+  if (typeof define === "function" && define.amd) {
+    // AMD. Register as an anonymous module.
+    define([], function() {
+      /*eslint-disable no-return-assign */
+      return (root.secrets = factory());
+      /*eslint-enable no-return-assign */
+    });
+  } else if (typeof exports === "object") {
+    // Node. Does not work with strict CommonJS, but
+    // only CommonJS-like environments that support module.exports,
+    // like Node.
+    module.exports = factory(require("crypto"));
+  } else {
+    // Browser globals (root is window)
+    root.secrets = factory(root.crypto);
+  }
+})(this, function() {
+  "use strict";
+  let crypto;
+  try {
+    crypto = require("crypto");
+  } catch (err) {
+    console.warn("crypto support is disabled!");
+  }
+
+  if (!crypto) {
+    crypto = window.crypto;
+  }
+
+  var defaults, config, preGenPadding, runCSPRNGTest, CSPRNGTypes;
+
+  function reset() {
+    defaults = {
+      bits: 8, // default number of bits
+      radix: 16, // work with HEX by default
+      minBits: 3,
+      maxBits: 20, // this permits 1,048,575 shares, though going this high is NOT recommended in JS!
+      bytesPerChar: 2,
+      maxBytesPerChar: 6, // Math.pow(256,7) > Math.pow(2,53)
+
+      // Primitive polynomials (in decimal form) for Galois Fields GF(2^n), for 2 <= n <= 30
+      // The index of each term in the array corresponds to the n for that polynomial
+      // i.e. to get the polynomial for n=16, use primitivePolynomials[16]
+      primitivePolynomials: [
+        null,
+        null,
+        1,
+        3,
+        3,
+        5,
+        3,
+        3,
+        29,
+        17,
+        9,
+        5,
+        83,
+        27,
+        43,
+        3,
+        45,
+        9,
+        39,
+        39,
+        9,
+        5,
+        3,
+        33,
+        27,
+        9,
+        71,
+        39,
+        9,
+        5,
+        83
+      ]
+    };
+    config = {};
+    preGenPadding = new Array(1024).join("0"); // Pre-generate a string of 1024 0's for use by padLeft().
+    runCSPRNGTest = true;
+
+    // WARNING : Never use 'testRandom' except for testing.
+    CSPRNGTypes = [
+      "nodeCryptoRandomBytes",
+      "browserCryptoGetRandomValues",
+      "testRandom"
+    ];
+  }
+
+  function isSetRNG() {
+    if (config && config.rng && typeof config.rng === "function") {
+      return true;
+    }
+
+    return false;
+  }
+
+  // Pads a string `str` with zeros on the left so that its length is a multiple of `bits`
+  function padLeft(str, multipleOfBits) {
+    var missing;
+
+    if (multipleOfBits === 0 || multipleOfBits === 1) {
+      return str;
+    }
+
+    if (multipleOfBits && multipleOfBits > 1024) {
+      throw new Error("Padding must be multiples of no larger than 1024 bits.");
+    }
+
+    multipleOfBits = multipleOfBits || config.bits;
+
+    if (str) {
+      missing = str.length % multipleOfBits;
+    }
+
+    if (missing) {
+      return (preGenPadding + str).slice(
+        -(multipleOfBits - missing + str.length)
+      );
+    }
+
+    return str;
+  }
+
+  function hex2bin(str) {
+    var bin = "",
+      num,
+      i;
+
+    for (i = str.length - 1; i >= 0; i--) {
+      num = parseInt(str[i], 16);
+
+      if (isNaN(num)) {
+        throw new Error("Invalid hex character.");
+      }
+
+      bin = padLeft(num.toString(2), 4) + bin;
+    }
+    return bin;
+  }
+
+  function bin2hex(str) {
+    var hex = "",
+      num,
+      i;
+
+    str = padLeft(str, 4);
+
+    for (i = str.length; i >= 4; i -= 4) {
+      num = parseInt(str.slice(i - 4, i), 2);
+      if (isNaN(num)) {
+        throw new Error("Invalid binary character.");
+      }
+      hex = num.toString(16) + hex;
+    }
+
+    return hex;
+  }
+
+  // Browser supports crypto.getRandomValues()
+  function hasCryptoGetRandomValues() {
+    if (
+      crypto &&
+      typeof crypto === "object" &&
+      (typeof crypto.getRandomValues === "function" ||
+        typeof crypto.getRandomValues === "object") &&
+      (typeof Uint32Array === "function" || typeof Uint32Array === "object")
+    ) {
+      return true;
+    }
+
+    return false;
+  }
+
+  // Node.js support for crypto.randomBytes()
+  function hasCryptoRandomBytes() {
+    if (
+      typeof crypto === "object" &&
+      typeof crypto.randomBytes === "function"
+    ) {
+      return true;
+    }
+
+    return false;
+  }
+
+  // Returns a pseudo-random number generator of the form function(bits){}
+  // which should output a random string of 1's and 0's of length `bits`.
+  // `type` (Optional) : A string representing the CSPRNG that you want to
+  // force to be loaded, overriding feature detection. Can be one of:
+  //    "nodeCryptoRandomBytes"
+  //    "browserCryptoGetRandomValues"
+  //
+  function getRNG(type) {
+    function construct(bits, arr, radix, size) {
+      var i = 0,
+        len,
+        str = "",
+        parsedInt;
+
+      if (arr) {
+        len = arr.length - 1;
+      }
+
+      while (i < len || str.length < bits) {
+        // convert any negative nums to positive with Math.abs()
+        parsedInt = Math.abs(parseInt(arr[i], radix));
+        str = str + padLeft(parsedInt.toString(2), size);
+        i++;
+      }
+
+      str = str.substr(-bits);
+
+      // return null so this result can be re-processed if the result is all 0's.
+      if ((str.match(/0/g) || []).length === str.length) {
+        return null;
+      }
+
+      return str;
+    }
+
+    // Node.js : crypto.randomBytes()
+    // Note : Node.js and crypto.randomBytes() uses the OpenSSL RAND_bytes() function for its CSPRNG.
+    //        Node.js will need to have been compiled with OpenSSL for this to work.
+    // See : https://github.com/joyent/node/blob/d8baf8a2a4481940bfed0196308ae6189ca18eee/src/node_crypto.cc#L4696
+    // See : https://www.openssl.org/docs/crypto/rand.html
+    function nodeCryptoRandomBytes(bits) {
+      var buf,
+        bytes,
+        radix,
+        size,
+        str = null;
+
+      radix = 16;
+      size = 4;
+      bytes = Math.ceil(bits / 8);
+
+      while (str === null) {
+        buf = crypto.randomBytes(bytes);
+        str = construct(bits, buf.toString("hex"), radix, size);
+      }
+
+      return str;
+    }
+
+    // Browser : crypto.getRandomValues()
+    // See : https://dvcs.w3.org/hg/webcrypto-api/raw-file/tip/spec/Overview.html#dfn-Crypto
+    // See : https://developer.mozilla.org/en-US/docs/Web/API/RandomSource/getRandomValues
+    // Supported Browsers : http://caniuse.com/#search=crypto.getRandomValues
+    function browserCryptoGetRandomValues(bits) {
+      var elems,
+        radix,
+        size,
+        str = null;
+
+      radix = 10;
+      size = 32;
+      elems = Math.ceil(bits / 32);
+      while (str === null) {
+        str = construct(
+          bits,
+          crypto.getRandomValues(new Uint32Array(elems)),
+          radix,
+          size
+        );
+      }
+
+      return str;
+    }
+
+    // /////////////////////////////////////////////////////////////
+    // WARNING : DO NOT USE. For testing purposes only.
+    // /////////////////////////////////////////////////////////////
+    // This function will return repeatable non-random test bits. Can be used
+    // for testing only. Node.js does not return proper random bytes
+    // when run within a PhantomJS container.
+    function testRandom(bits) {
+      var arr,
+        elems,
+        int,
+        radix,
+        size,
+        str = null;
+
+      radix = 10;
+      size = 32;
+      elems = Math.ceil(bits / 32);
+      int = 123456789;
+      arr = new Uint32Array(elems);
+
+      // Fill every element of the Uint32Array with the same int.
+      for (var i = 0; i < arr.length; i++) {
+        arr[i] = int;
+      }
+
+      while (str === null) {
+        str = construct(bits, arr, radix, size);
+      }
+
+      return str;
+    }
+
+    // Return a random generator function for browsers that support
+    // crypto.getRandomValues() or Node.js compiled with OpenSSL support.
+    // WARNING : NEVER use testRandom outside of a testing context. Totally non-random!
+    if (type && type === "testRandom") {
+      config.typeCSPRNG = type;
+      return testRandom;
+    } else if (type && type === "nodeCryptoRandomBytes") {
+      config.typeCSPRNG = type;
+      return nodeCryptoRandomBytes;
+    } else if (type && type === "browserCryptoGetRandomValues") {
+      config.typeCSPRNG = type;
+      return browserCryptoGetRandomValues;
+    } else if (hasCryptoRandomBytes()) {
+      config.typeCSPRNG = "nodeCryptoRandomBytes";
+      return nodeCryptoRandomBytes;
+    } else if (hasCryptoGetRandomValues()) {
+      config.typeCSPRNG = "browserCryptoGetRandomValues";
+      return browserCryptoGetRandomValues;
+    }
+  }
+
+  // Splits a number string `bits`-length segments, after first
+  // optionally zero-padding it to a length that is a multiple of `padLength.
+  // Returns array of integers (each less than 2^bits-1), with each element
+  // representing a `bits`-length segment of the input string from right to left,
+  // i.e. parts[0] represents the right-most `bits`-length segment of the input string.
+  function splitNumStringToIntArray(str, padLength) {
+    var parts = [],
+      i;
+
+    if (padLength) {
+      str = padLeft(str, padLength);
+    }
+
+    for (i = str.length; i > config.bits; i -= config.bits) {
+      parts.push(parseInt(str.slice(i - config.bits, i), 2));
+    }
+
+    parts.push(parseInt(str.slice(0, i), 2));
+
+    return parts;
+  }
+
+  // Polynomial evaluation at `x` using Horner's Method
+  // NOTE: fx=fx * x + coeff[i] ->  exp(log(fx) + log(x)) + coeff[i],
+  //       so if fx===0, just set fx to coeff[i] because
+  //       using the exp/log form will result in incorrect value
+  function horner(x, coeffs) {
+    var logx = config.logs[x],
+      fx = 0,
+      i;
+
+    for (i = coeffs.length - 1; i >= 0; i--) {
+      if (fx !== 0) {
+        fx =
+          config.exps[(logx + config.logs[fx]) % config.maxShares] ^ coeffs[i];
+      } else {
+        fx = coeffs[i];
+      }
+    }
+
+    return fx;
+  }
+
+  // Evaluate the Lagrange interpolation polynomial at x = `at`
+  // using x and y Arrays that are of the same length, with
+  // corresponding elements constituting points on the polynomial.
+  function lagrange(at, x, y) {
+    var sum = 0,
+      len,
+      product,
+      i,
+      j;
+
+    for (i = 0, len = x.length; i < len; i++) {
+      if (y[i]) {
+        product = config.logs[y[i]];
+
+        for (j = 0; j < len; j++) {
+          if (i !== j) {
+            if (at === x[j]) {
+              // happens when computing a share that is in the list of shares used to compute it
+              product = -1; // fix for a zero product term, after which the sum should be sum^0 = sum, not sum^1
+              break;
+            }
+            product =
+              (product +
+                config.logs[at ^ x[j]] -
+                config.logs[x[i] ^ x[j]] +
+                config.maxShares) %
+              config.maxShares; // to make sure it's not negative
+          }
+        }
+
+        // though exps[-1] === undefined and undefined ^ anything = anything in
+        // chrome, this behavior may not hold everywhere, so do the check
+        sum = product === -1 ? sum : sum ^ config.exps[product];
+      }
+    }
+
+    return sum;
+  }
+
+  // This is the basic polynomial generation and evaluation function
+  // for a `config.bits`-length secret (NOT an arbitrary length)
+  // Note: no error-checking at this stage! If `secret` is NOT
+  // a NUMBER less than 2^bits-1, the output will be incorrect!
+  function getShares(secret, numShares, threshold) {
+    var shares = [],
+      coeffs = [secret],
+      i,
+      len;
+
+    for (i = 1; i < threshold; i++) {
+      coeffs[i] = parseInt(config.rng(config.bits), 2);
+    }
+
+    for (i = 1, len = numShares + 1; i < len; i++) {
+      shares[i - 1] = {
+        x: i,
+        y: horner(i, coeffs)
+      };
+    }
+
+    return shares;
+  }
+
+  function constructPublicShareString(bits, id, data) {
+    var bitsBase36, idHex, idMax, idPaddingLen, newShareString;
+
+    id = parseInt(id, config.radix);
+    bits = parseInt(bits, 10) || config.bits;
+    bitsBase36 = bits.toString(36).toUpperCase();
+    idMax = Math.pow(2, bits) - 1;
+    idPaddingLen = idMax.toString(config.radix).length;
+    idHex = padLeft(id.toString(config.radix), idPaddingLen);
+
+    if (typeof id !== "number" || id % 1 !== 0 || id < 1 || id > idMax) {
+      throw new Error(
+        "Share id must be an integer between 1 and " + idMax + ", inclusive."
+      );
+    }
+
+    newShareString = bitsBase36 + idHex + data;
+
+    return newShareString;
+  }
+
+  // EXPORTED FUNCTIONS
+  // //////////////////
+
+  var secrets = {
+    init: function(bits, rngType) {
+      var logs = [],
+        exps = [],
+        x = 1,
+        primitive,
+        i;
+
+      // reset all config back to initial state
+      reset();
+
+      if (
+        bits &&
+        (typeof bits !== "number" ||
+          bits % 1 !== 0 ||
+          bits < defaults.minBits ||
+          bits > defaults.maxBits)
+      ) {
+        throw new Error(
+          "Number of bits must be an integer between " +
+            defaults.minBits +
+            " and " +
+            defaults.maxBits +
+            ", inclusive."
+        );
+      }
+
+      if (rngType && CSPRNGTypes.indexOf(rngType) === -1) {
+        throw new Error("Invalid RNG type argument : '" + rngType + "'");
+      }
+
+      config.radix = defaults.radix;
+      config.bits = bits || defaults.bits;
+      config.size = Math.pow(2, config.bits);
+      config.maxShares = config.size - 1;
+
+      // Construct the exp and log tables for multiplication.
+      primitive = defaults.primitivePolynomials[config.bits];
+
+      for (i = 0; i < config.size; i++) {
+        exps[i] = x;
+        logs[x] = i;
+        x = x << 1; // Left shift assignment
+        if (x >= config.size) {
+          x = x ^ primitive; // Bitwise XOR assignment
+          x = x & config.maxShares; // Bitwise AND assignment
+        }
+      }
+
+      config.logs = logs;
+      config.exps = exps;
+
+      if (rngType) {
+        this.setRNG(rngType);
+      }
+
+      if (!isSetRNG()) {
+        this.setRNG();
+      }
+
+      if (
+        !isSetRNG() ||
+        !config.bits ||
+        !config.size ||
+        !config.maxShares ||
+        !config.logs ||
+        !config.exps ||
+        config.logs.length !== config.size ||
+        config.exps.length !== config.size
+      ) {
+        throw new Error("Initialization failed.");
+      }
+    },
+
+    // Evaluates the Lagrange interpolation polynomial at x=`at` for
+    // individual config.bits-length segments of each share in the `shares`
+    // Array. Each share is expressed in base `inputRadix`. The output
+    // is expressed in base `outputRadix'.
+    combine: function(shares, at) {
+      var i,
+        j,
+        len,
+        len2,
+        result = "",
+        setBits,
+        share,
+        splitShare,
+        x = [],
+        y = [];
+
+      at = at || 0;
+
+      for (i = 0, len = shares.length; i < len; i++) {
+        share = this.extractShareComponents(shares[i]);
+
+        // All shares must have the same bits settings.
+        if (setBits === undefined) {
+          setBits = share.bits;
+        } else if (share.bits !== setBits) {
+          throw new Error("Mismatched shares: Different bit settings.");
+        }
+
+        // Reset everything to the bit settings of the shares.
+        if (config.bits !== setBits) {
+          this.init(setBits);
+        }
+
+        // Proceed if this share.id is not already in the Array 'x' and
+        // then split each share's hex data into an Array of Integers,
+        // then 'rotate' those arrays where the first element of each row is converted to
+        // its own array, the second element of each to its own Array, and so on for all of the rest.
+        // Essentially zipping all of the shares together.
+        //
+        // e.g.
+        //   [ 193, 186, 29, 150, 5, 120, 44, 46, 49, 59, 6, 1, 102, 98, 177, 196 ]
+        //   [ 53, 105, 139, 49, 187, 240, 91, 92, 98, 118, 12, 2, 204, 196, 127, 149 ]
+        //   [ 146, 211, 249, 167, 209, 136, 118, 114, 83, 77, 10, 3, 170, 166, 206, 81 ]
+        //
+        // becomes:
+        //
+        // [ [ 193, 53, 146 ],
+        //   [ 186, 105, 211 ],
+        //   [ 29, 139, 249 ],
+        //   [ 150, 49, 167 ],
+        //   [ 5, 187, 209 ],
+        //   [ 120, 240, 136 ],
+        //   [ 44, 91, 118 ],
+        //   [ 46, 92, 114 ],
+        //   [ 49, 98, 83 ],
+        //   [ 59, 118, 77 ],
+        //   [ 6, 12, 10 ],
+        //   [ 1, 2, 3 ],
+        //   [ 102, 204, 170 ],
+        //   [ 98, 196, 166 ],
+        //   [ 177, 127, 206 ],
+        //   [ 196, 149, 81 ] ]
+        //
+        if (x.indexOf(share.id) === -1) {
+          x.push(share.id);
+          splitShare = splitNumStringToIntArray(hex2bin(share.data));
+          for (j = 0, len2 = splitShare.length; j < len2; j++) {
+            y[j] = y[j] || [];
+            y[j][x.length - 1] = splitShare[j];
+          }
+        }
+      }
+
+      // Extract the secret from the 'rotated' share data and return a
+      // string of Binary digits which represent the secret directly. or in the
+      // case of a newShare() return the binary string representing just that
+      // new share.
+      for (i = 0, len = y.length; i < len; i++) {
+        result = padLeft(lagrange(at, x, y[i]).toString(2)) + result;
+      }
+
+      // If 'at' is non-zero combine() was called from newShare(). In this
+      // case return the result (the new share data) directly.
+      //
+      // Otherwise find the first '1' which was added in the share() function as a padding marker
+      // and return only the data after the padding and the marker. Convert this Binary string
+      // to hex, which represents the final secret result (which can be converted from hex back
+      // to the original string in user space using `hex2str()`).
+      return bin2hex(at >= 1 ? result : result.slice(result.indexOf("1") + 1));
+    },
+
+    getConfig: function() {
+      var obj = {};
+      obj.radix = config.radix;
+      obj.bits = config.bits;
+      obj.maxShares = config.maxShares;
+      obj.hasCSPRNG = isSetRNG();
+      obj.typeCSPRNG = config.typeCSPRNG;
+      return obj;
+    },
+
+    // Given a public share, extract the bits (Integer), share ID (Integer), and share data (Hex)
+    // and return an Object containing those components.
+    extractShareComponents: function(share) {
+      var bits,
+        id,
+        idLen,
+        max,
+        obj = {},
+        regexStr,
+        shareComponents;
+
+      // Extract the first char which represents the bits in Base 36
+      bits = parseInt(share.substr(0, 1), 36);
+
+      if (
+        bits &&
+        (typeof bits !== "number" ||
+          bits % 1 !== 0 ||
+          bits < defaults.minBits ||
+          bits > defaults.maxBits)
+      ) {
+        throw new Error(
+          "Invalid share : Number of bits must be an integer between " +
+            defaults.minBits +
+            " and " +
+            defaults.maxBits +
+            ", inclusive."
+        );
+      }
+
+      // calc the max shares allowed for given bits
+      max = Math.pow(2, bits) - 1;
+
+      // Determine the ID length which is variable and based on the bit count.
+      idLen = (Math.pow(2, bits) - 1).toString(config.radix).length;
+
+      // Extract all the parts now that the segment sizes are known.
+      regexStr = "^([a-kA-K3-9]{1})([a-fA-F0-9]{" + idLen + "})([a-fA-F0-9]+)$";
+      shareComponents = new RegExp(regexStr).exec(share);
+
+      // The ID is a Hex number and needs to be converted to an Integer
+      if (shareComponents) {
+        id = parseInt(shareComponents[2], config.radix);
+      }
+
+      if (typeof id !== "number" || id % 1 !== 0 || id < 1 || id > max) {
+        throw new Error(
+          "Invalid share : Share id must be an integer between 1 and " +
+            config.maxShares +
+            ", inclusive."
+        );
+      }
+
+      if (shareComponents && shareComponents[3]) {
+        obj.bits = bits;
+        obj.id = id;
+        obj.data = shareComponents[3];
+        return obj;
+      }
+
+      throw new Error("The share data provided is invalid : " + share);
+    },
+
+    // Set the PRNG to use. If no RNG function is supplied, pick a default using getRNG()
+    setRNG: function(rng) {
+      var errPrefix = "Random number generator is invalid ",
+        errSuffix =
+          " Supply an CSPRNG of the form function(bits){} that returns a string containing 'bits' number of random 1's and 0's.";
+
+      if (rng && typeof rng === "string" && CSPRNGTypes.indexOf(rng) === -1) {
+        throw new Error("Invalid RNG type argument : '" + rng + "'");
+      }
+
+      // If RNG was not specified at all,
+      // try to pick one appropriate for this env.
+      if (!rng) {
+        rng = getRNG();
+      }
+
+      // If `rng` is a string, try to forcibly
+      // set the RNG to the type specified.
+      if (rng && typeof rng === "string") {
+        rng = getRNG(rng);
+      }
+
+      if (runCSPRNGTest) {
+        if (rng && typeof rng !== "function") {
+          throw new Error(errPrefix + "(Not a function)." + errSuffix);
+        }
+
+        if (rng && typeof rng(config.bits) !== "string") {
+          throw new Error(errPrefix + "(Output is not a string)." + errSuffix);
+        }
+
+        if (rng && !parseInt(rng(config.bits), 2)) {
+          throw new Error(
+            errPrefix +
+              "(Binary string output not parseable to an Integer)." +
+              errSuffix
+          );
+        }
+
+        if (rng && rng(config.bits).length > config.bits) {
+          throw new Error(
+            errPrefix +
+              "(Output length is greater than config.bits)." +
+              errSuffix
+          );
+        }
+
+        if (rng && rng(config.bits).length < config.bits) {
+          throw new Error(
+            errPrefix + "(Output length is less than config.bits)." + errSuffix
+          );
+        }
+      }
+
+      config.rng = rng;
+
+      return true;
+    },
+
+    // Converts a given UTF16 character string to the HEX representation.
+    // Each character of the input string is represented by
+    // `bytesPerChar` bytes in the output string which defaults to 2.
+    str2hex: function(str, bytesPerChar) {
+      var hexChars,
+        max,
+        out = "",
+        neededBytes,
+        num,
+        i,
+        len;
+
+      if (typeof str !== "string") {
+        throw new Error("Input must be a character string.");
+      }
+
+      if (!bytesPerChar) {
+        bytesPerChar = defaults.bytesPerChar;
+      }
+
+      if (
+        typeof bytesPerChar !== "number" ||
+        bytesPerChar < 1 ||
+        bytesPerChar > defaults.maxBytesPerChar ||
+        bytesPerChar % 1 !== 0
+      ) {
+        throw new Error(
+          "Bytes per character must be an integer between 1 and " +
+            defaults.maxBytesPerChar +
+            ", inclusive."
+        );
+      }
+
+      hexChars = 2 * bytesPerChar;
+      max = Math.pow(16, hexChars) - 1;
+
+      for (i = 0, len = str.length; i < len; i++) {
+        num = str[i].charCodeAt();
+
+        if (isNaN(num)) {
+          throw new Error("Invalid character: " + str[i]);
+        }
+
+        if (num > max) {
+          neededBytes = Math.ceil(Math.log(num + 1) / Math.log(256));
+          throw new Error(
+            "Invalid character code (" +
+              num +
+              "). Maximum allowable is 256^bytes-1 (" +
+              max +
+              "). To convert this character, use at least " +
+              neededBytes +
+              " bytes."
+          );
+        }
+
+        out = padLeft(num.toString(16), hexChars) + out;
+      }
+      return out;
+    },
+
+    // Converts a given HEX number string to a UTF16 character string.
+    hex2str: function(str, bytesPerChar) {
+      var hexChars,
+        out = "",
+        i,
+        len;
+
+      if (typeof str !== "string") {
+        throw new Error("Input must be a hexadecimal string.");
+      }
+      bytesPerChar = bytesPerChar || defaults.bytesPerChar;
+
+      if (
+        typeof bytesPerChar !== "number" ||
+        bytesPerChar % 1 !== 0 ||
+        bytesPerChar < 1 ||
+        bytesPerChar > defaults.maxBytesPerChar
+      ) {
+        throw new Error(
+          "Bytes per character must be an integer between 1 and " +
+            defaults.maxBytesPerChar +
+            ", inclusive."
+        );
+      }
+
+      hexChars = 2 * bytesPerChar;
+
+      str = padLeft(str, hexChars);
+
+      for (i = 0, len = str.length; i < len; i += hexChars) {
+        out =
+          String.fromCharCode(parseInt(str.slice(i, i + hexChars), 16)) + out;
+      }
+
+      return out;
+    },
+
+    // Generates a random bits-length number string using the PRNG
+    random: function(bits) {
+      if (
+        typeof bits !== "number" ||
+        bits % 1 !== 0 ||
+        bits < 2 ||
+        bits > 65536
+      ) {
+        throw new Error(
+          "Number of bits must be an Integer between 1 and 65536."
+        );
+      }
+
+      return bin2hex(config.rng(bits));
+    },
+
+    // Divides a `secret` number String str expressed in radix `inputRadix` (optional, default 16)
+    // into `numShares` shares, each expressed in radix `outputRadix` (optional, default to `inputRadix`),
+    // requiring `threshold` number of shares to reconstruct the secret.
+    // Optionally, zero-pads the secret to a length that is a multiple of padLength before sharing.
+    share: function(secret, numShares, threshold, padLength) {
+      var neededBits,
+        subShares,
+        x = new Array(numShares),
+        y = new Array(numShares),
+        i,
+        j,
+        len;
+
+      // Security:
+      // For additional security, pad in multiples of 128 bits by default.
+      // A small trade-off in larger share size to help prevent leakage of information
+      // about small-ish secrets and increase the difficulty of attacking them.
+      padLength = padLength || 128;
+
+      if (typeof secret !== "string") {
+        throw new Error("Secret must be a string.");
+      }
+
+      if (
+        typeof numShares !== "number" ||
+        numShares % 1 !== 0 ||
+        numShares < 2
+      ) {
+        throw new Error(
+          "Number of shares must be an integer between 2 and 2^bits-1 (" +
+            config.maxShares +
+            "), inclusive."
+        );
+      }
+
+      if (numShares > config.maxShares) {
+        neededBits = Math.ceil(Math.log(numShares + 1) / Math.LN2);
+        throw new Error(
+          "Number of shares must be an integer between 2 and 2^bits-1 (" +
+            config.maxShares +
+            "), inclusive. To create " +
+            numShares +
+            " shares, use at least " +
+            neededBits +
+            " bits."
+        );
+      }
+
+      if (
+        typeof threshold !== "number" ||
+        threshold % 1 !== 0 ||
+        threshold < 2
+      ) {
+        throw new Error(
+          "Threshold number of shares must be an integer between 2 and 2^bits-1 (" +
+            config.maxShares +
+            "), inclusive."
+        );
+      }
+
+      if (threshold > config.maxShares) {
+        neededBits = Math.ceil(Math.log(threshold + 1) / Math.LN2);
+        throw new Error(
+          "Threshold number of shares must be an integer between 2 and 2^bits-1 (" +
+            config.maxShares +
+            "), inclusive.  To use a threshold of " +
+            threshold +
+            ", use at least " +
+            neededBits +
+            " bits."
+        );
+      }
+
+      if (threshold > numShares) {
+        throw new Error(
+          "Threshold number of shares was " +
+            threshold +
+            " but must be less than or equal to the " +
+            numShares +
+            " shares specified as the total to generate."
+        );
+      }
+
+      if (
+        typeof padLength !== "number" ||
+        padLength % 1 !== 0 ||
+        padLength < 0 ||
+        padLength > 1024
+      ) {
+        throw new Error(
+          "Zero-pad length must be an integer between 0 and 1024 inclusive."
+        );
+      }
+
+      secret = "1" + hex2bin(secret); // prepend a 1 as a marker so that we can preserve the correct number of leading zeros in our secret
+      secret = splitNumStringToIntArray(secret, padLength);
+
+      for (i = 0, len = secret.length; i < len; i++) {
+        subShares = getShares(secret[i], numShares, threshold);
+        for (j = 0; j < numShares; j++) {
+          x[j] = x[j] || subShares[j].x.toString(config.radix);
+          y[j] = padLeft(subShares[j].y.toString(2)) + (y[j] || "");
+        }
+      }
+
+      for (i = 0; i < numShares; i++) {
+        x[i] = constructPublicShareString(config.bits, x[i], bin2hex(y[i]));
+      }
+
+      return x;
+    },
+
+    // Generate a new share with id `id` (a number between 1 and 2^bits-1)
+    // `id` can be a Number or a String in the default radix (16)
+    newShare: function(id, shares) {
+      var share, radid;
+
+      if (id && typeof id === "string") {
+        id = parseInt(id, config.radix);
+      }
+
+      radid = id.toString(config.radix);
+
+      if (id && radid && shares && shares[0]) {
+        share = this.extractShareComponents(shares[0]);
+        return constructPublicShareString(
+          share.bits,
+          radid,
+          this.combine(shares, id)
+        );
+      }
+
+      throw new Error("Invalid 'id' or 'shares' Array argument to newShare().");
+    },
+
+    /* test-code */
+    // export private functions so they can be unit tested directly.
+    _reset: reset,
+    _padLeft: padLeft,
+    _hex2bin: hex2bin,
+    _bin2hex: bin2hex,
+    _hasCryptoGetRandomValues: hasCryptoGetRandomValues,
+    _hasCryptoRandomBytes: hasCryptoRandomBytes,
+    _getRNG: getRNG,
+    _isSetRNG: isSetRNG,
+    _splitNumStringToIntArray: splitNumStringToIntArray,
+    _horner: horner,
+    _lagrange: lagrange,
+    _getShares: getShares,
+    _constructPublicShareString: constructPublicShareString
+    /* end-test-code */
+  };
+
+  // Always initialize secrets with default settings.
+  secrets.init();
+
+  return secrets;
+});
diff --git a/javascript/src/utilities/numberUtilities.js b/javascript/src/utilities/numberUtilities.js
new file mode 100644
index 0000000000000000000000000000000000000000..dbee13839522df1f54fe79ff2fa57befd736ae68
--- /dev/null
+++ b/javascript/src/utilities/numberUtilities.js
@@ -0,0 +1,14 @@
+export function getRandomInt(max) {
+  return Math.floor(Math.random() * Math.floor(max));
+}
+
+export function getSliceRange(max) {
+  const beginIndex = getRandomInt(max);
+  const endIndex = getRandomInt(max);
+
+  if (beginIndex === endIndex) {
+    return getSliceRange(max);
+  }
+
+  return { beginIndex, endIndex };
+}
diff --git a/javascript/src/utilities/secrets.js b/javascript/src/utilities/secrets.js
new file mode 100644
index 0000000000000000000000000000000000000000..2223ae6533e57d2573b3a473e94f0c7e8287adfd
--- /dev/null
+++ b/javascript/src/utilities/secrets.js
@@ -0,0 +1,98 @@
+import secrets from "../lib/secrets";
+import { encryptMessage } from "./signingUtilities";
+import { encodeResponse } from "./appUtility";
+import { getSliceRange } from "./numberUtilities";
+import { THRESHOLD } from "../constants/secrets";
+
+/** Initialize
+ */
+export const initSecrets = (bits, rngType) => secrets.init(bits, rngType);
+
+export const setRNG = rngType => secrets.setRNG(rngType);
+export const getSecretsConfig = () => secrets.getConfig();
+
+/**
+ * Function generates a random bits length string, and output it in hexadecimal format
+ *
+ * @param {number} bits
+ * @returns {string} hex
+ */
+export const generateSecret = bits => secrets.random(bits);
+
+/**
+ * Divide a secret expressed in hexadecimal form into numShares number of shares, requiring that threshold number of shares be present for reconstructing the secret
+ *
+ * @param {string} secret
+ * @param {number} numShares
+ * @param {number} threshold
+ * @param {number} [padLength=128]
+ * @returns {array}
+ */
+export const divideSecretToShares = (
+  secret,
+  numShares,
+  threshold,
+  padLength = 128
+) => secrets.share(secret, numShares, threshold, padLength);
+
+/**
+ * Reconstructs a secret from shares
+ *
+ * @param {array} shares
+ * @returns {string}
+ */
+export const combineSecret = shares => secrets.combine(shares);
+
+export const encryptShare = async (share, publicKey) =>
+  await encryptMessage(share, publicKey, "secretPart");
+
+/** Account Recovery key management */
+
+export const generateRecoveryKey = () => {
+  const recoveryKey = generateSecret(512);
+  return recoveryKey;
+};
+
+export const getRecoveryKeyShares = (recoveryKey, sharesNumber) => {
+  return divideSecretToShares(recoveryKey, sharesNumber, THRESHOLD);
+};
+
+function getSecretSliceRange(max) {
+  const { beginIndex, endIndex } = getSliceRange(max);
+  if (endIndex - beginIndex < THRESHOLD) {
+    return getSecretSliceRange(max);
+  }
+
+  return { beginIndex, endIndex };
+}
+
+export const checkRecoveryKeyCombine = (recoveryKey, recoveryKeyShares) => {
+  let checkKey;
+
+  const { beginIndex, endIndex } = getSecretSliceRange(
+    recoveryKeyShares.length + 1
+  );
+
+  checkKey = combineSecret(recoveryKeyShares.slice(beginIndex, endIndex));
+  if (checkKey !== recoveryKey) {
+    return encodeResponse(
+      "400",
+      "",
+      "Sanity check with required number of shares failed"
+    );
+  }
+  checkKey = combineSecret(recoveryKeyShares.slice(0, 1));
+  if (checkKey === recoveryKey) {
+    return encodeResponse(
+      "400",
+      "",
+      "Sanity check with less than required shares failed"
+    );
+  }
+  checkKey = combineSecret(recoveryKeyShares);
+  if (checkKey !== recoveryKey) {
+    return encodeResponse("400", "", "Sanity check with all shares failed");
+  }
+
+  return encodeResponse("200", "", "Check passed");
+};