Skip to main content

Threshold Device Key Management

tKey manages private keys using the user’s device, private input, and wallet service provider. As long as a user has access to 2 out of 3 (2/3) of these shares, they will be able to retrieve their private key. For more information, checkout the technical overview. Before integrating you can also checkout the example site for tKey.


Packages#

Packages@latest VersionSizeDescription
🏠 Core
@tkey/corenpm versionminzipCore functionalities for creating a tkey
@tkey/service-provider-basenpm versionminzipAccepts a private key which can be used to create one of the shares the tkey
🔌 Modules
@tkey/chrome-storagenpm versionminzipAdd/remove a share from chrome extension storage
@tkey/web-storagenpm versionminzipAdd/remove a share from local and file storage
@tkey/security-questionsnpm versionminzipAdd/remove a security question and password as a share for tkey
@tkey/share-transfernpm versionminzipTransfer share from another device
@tkey/seed-phrasenpm versionminzipStore and use seedphrases on metadata
@tkey/private-keysnpm versionminzipStore extra private keys on tKey metadata
@tkey/share-serializationnpm versionminzipImport/export a share from tKey
🐉 Torus
@tkey/defaultnpm versionminzipBundles Core and Modules into one importable package
@tkey/service-provider-torusnpm versionminzip@service-provider-base with DirectAuth functionality
@tkey/storage-layer-torusnpm versionminzipget/set metadata for various shares
🐉 Low-Level
@tkey/common-typesnpm versionminzipShared TypeScript Types

Usage#

Pre-cursors#

Before including the tKey SDK, we first need to setup directAuth for the Google logins etc... Below are several steps:

npm i @tkey/default

  1. If you're using redirectToOpener, modify the origin of postMessage from "http://localhost:3000" to your hosted domain in redirect.html and sw.js

  2. Serve service worker from baseUrl where baseUrl is the one passed while instantiating tkey for specific login (example http://localhost:3000/serviceworker/). If you're already using a sw, please ensure to port over the fetch override from our service worker

  3. For browsers where service workers are not supported or if you wish not to use service workers, create and serve redirect page from baseUrl/redirect where baseUrl is the one passed while instantiating tkey for specific login ( example http://localhost:3000/serviceworker/)

  4. At verifier's interface (where you obtain client id), please use baseUrl/redirect (eg: http://localhost:3000/serviceworker/redirect) as the redirect_uri where baseUrl is the one passed while instantiating tkey

Now we can proceed to the basic usage. For your own applications, please signup on developer.tor.us

Basic Usage#

Packages who wish to use torus defaults can use @tkey/default to initialize

import ThresholdKey from "@tkey/default";import WebStorageModule, { WEB_STORAGE_MODULE_NAME } from "@tkey/web-storage";
// Torus service provider uses directAuth to fetch users private key from the set of Torus nodes. This private key is one of the share in TSS.// directAuth requires a deployment of a verifier with your clientId. Use developer.tor.us to create your verifier.// Can use ServiceProviderBase which takes private key as input insteadconst serviceProvider = new TorusServiceProvider({  directParams: {    baseUrl: "<REDIRECT_URL>",    network: "testnet", // or mainnet    proxyContractAddress: "0x4023d2a0D330bF11426B12C6144Cfb96B7fa6183", // corresponding proxy contract address of the specified network  },});
// Storage layer used by the service provider// Can use Custom storage layer which fits IStorageLayer interfaceconst storageLayer = new TorusStorageLayer({ hostUrl: "https://metadata.tor.us", serviceProvider });
const tkey = new ThresholdKey({  modules: {    // More modules can be passed to create additional shares.    [WEB_STORAGE_MODULE_NAME]: new WebStorageModule(),  },  serviceProvider,  storageLayer,});
// triggers google login.// After google login succeeds, initialise tkey, metadata and its modules. (Minimum one share is required to read from the storage layer. In this case it was google login)// In case of web applications, we create another share and store it on browsers local storage. This makes the threshold 2/2. You can use modules to create additional sharesawait tkey.serviceProvider.init()await tkey.serviceProvider.triggerLogin();
/** * initialize({params}) * @param params? { * withShare?: ShareStore; // Initialize with specific share, by default service provider will be used * importKey?: BN; // Select specific private key to split * neverInitializeNewKey?: boolean; // Initialize the SDK only if tkey already exists * } * @returns KeyDetails */
await tkey.initialize({});
// Private key reconstruction. This is your threshold key.const reconstructedKey = await tkey.reconstructKey();

Creating 2/3 tkey#

Developers who wish to customize can use @tkey/core.


import SecurityQuestionsModule, { SECURITY_QUESTIONS_MODULE_NAME } from "@tkey/security-questions";
// Constructorconst tkey = new ThresholdKey({  modules: {    // More modules can be passed to create additional shares.    [SECURITY_QUESTIONS_MODULE_NAME]: new SecurityQuestionsModule(),    [WEB_STORAGE_MODULE_NAME]: new WebStorageModule(),  },  serviceProvider,  storageLayer,});
// triggers google login.await tkey.serviceProvider.init({ skipSw: true });await tkey.serviceProvider.triggerLogin();
await tkey.initialize();const reconstructedKey = await tkey.reconstructKey(); // created 2/2 tkey. Both shares will be required to reconstruct tkey.
// Creating a security question share.// Resulting threshold - 2/3. reconstructed key remains same.await tkey.modules.securityQuestions.generateNewShareWithSecurityQuestions("myanswer", "myquestion?");
// Creating a password share.// Resulting threshold - 2/3.await tkey.modules.securityQuestions.generateNewShareWithSecurityQuestions("mypassword", "what is your password?");

Usage of manualSync parameter.#

The manualSync parameter can be used to save the tkey transitions locally. Following are the benefits of using this parameter:

  1. This allows to create m/n threshold key in one step.
  2. For a multiscreen signup flow, you can serialize/deserialize the sdk from one page to another without pushing the changes to the cloud.
  3. Rollback to previous tkey state in case of unexpected errors.
// Constructorconst tkey = new ThresholdKey({  modules: {    // More modules can be passed to create additional shares.    [SECURITY_QUESTIONS_MODULE_NAME]: new SecurityQuestionsModule(),    [WEB_STORAGE_MODULE_NAME]: new WebStorageModule(),  },  serviceProvider,  storageLayer,  manualSync: true});
await tkey.initialize();const reconstructedKey = await tkey.reconstructKey(); // created 2/2 tkey. All changes are local.await tkey.modules.securityQuestions.generateNewShareWithSecurityQuestions("mypassword", "what is your password?"); // update threshold to 2/3. All changes are localawait tkey.syncLocalMetadataTransitions() // push metadata to cloud
// Rollback exampleawait tkey.generateNewShare()tkey = await tkey.updateSDK() // this will revert the share generated above

Export and import shares as mnemonics#

// Constructorconst tkey = new ThresholdKey({  modules: {    // Share serialization is included in `@tkey/default`. Import it explicitly if you are using `@tkey/core`    [SHARE_SERIALIZATION_MODULE_NAME]: new ShareSerializationModule(),  },  serviceProvider,  storageLayer,});
const exportedSeedShare = await tb.outputShare(resp1.deviceShare.share.shareIndex, "mnemonic"); // exported as 24-word mnemonic
await tb2.inputShare(exportedSeedShare.toString("hex"), "mnemonic"); // import share-mnemonic

Import seedphrases and private keys#

These imported private keys/seed phrases are encrypted (with threshold key) and stored on share's metadata. They have no relation to threshold key or shares. Usually, they are used by users with existing keys.

const metamaskSeedPhraseFormat = new MetamaskSeedPhraseFormat("https://mainnet.infura.io/v3/bca735fdbba0408bb09471e86463ae68");const privateKeyFormat = new SECP256k1Format();
// Constructorconst tkey = new ThresholdKey({  modules: {    seedPhrase: new SeedPhraseModule([metamaskSeedPhraseFormat]),    privateKeyModule: new PrivateKeyModule([privateKeyFormat])  },  serviceProvider,  storageLayer,});
// You will have to reconstruct key to get seedphrase/private keys backawait tkey.reconstructKey()
// get/set private keysconst privateKeys = [        new BN("4bd0041b7654a9b16a7268a5de7982f2422b15635c4fd170c140dc4897624390", "hex"),        new BN("1ea6edde61c750ec02896e9ac7fe9ac0b48a3630594fdf52ad5305470a2635c0", "hex"),      ];await tkey.modules.privateKeyModule.setPrivateKey("secp256k1n", privateKeys[0]);await tkey.modules.privateKeyModule.getAccounts();
// get/set seedphraseconst seedPhraseToSet = "object brass success calm lizard science syrup planet exercise parade honey impulse";await tkey.modules.seedPhrase.setSeedPhrase("HD Key Tree", seedPhraseToSet);const returnedSeed = await tkey.modules.seedPhrase.getSeedPhrases();

Advanced: Create 4/5 tkey#

There are many ways to create m/n threshold key. Following is an example of how to create a 4/5 tkey.

// Constructorconst tkey = new ThresholdKey({  modules: {    [WEB_STORAGE_MODULE_NAME]: new WebStorageModule(),  },  serviceProvider,  storageLayer,});
const resp1 = await tkey.initialize({}) // 2/2const { newShareStores: newShareStores1, newShareIndex: newShareIndex1 } = await tkey.generateNewShare(); // 2/3const { newShareStores: newShareStores2, newShareIndex: newShareIndex2 } = await tkey.generateNewShare(); // 2/4const { newShareIndex: newShareIndex3 } = await tkey.generateNewShare(); // 2/5await tkey.reconstructKey();
// Fetch existing share indexesconst pubPoly = tkey.metadata.getLatestPublicPolynomial();const previousPolyID = pubPoly.getPolynomialID();const existingShareIndexes = tkey.metadata.getShareIndexesForPolynomial(previousPolyID);
// increase thresold to 4. 4/5 tkey// _refreshShares() is an internal function. Use it with caution.await tkey._refreshShares(4, existingShareIndexes, previousPolyID);
// Reconstructionconst tkey2 = new ThresholdKey({ serviceProvider: defaultSP, storageLayer: defaultSL, manualSync: mode });await tkey2.initialize({ neverInitializeNewKey: true });tkey2.inputShareStore(resp1.deviceShare);tkey2.inputShareStore(newShareStores1[newShareIndex1.toString("hex")]);tkey2.inputShareStore(newShareStores2[newShareIndex2.toString("hex")]);await tkey2.reconstructKey();