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 CustomAuth 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 CustomAuth 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 customAuth to fetch users private key from the set of Torus nodes. This private key is one of the share in TSS.
// CustomAuth 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 instead
const 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 interface
const 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 shares
await 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";

// Constructor
const 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.
// Constructor
const 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 local
await tkey.syncLocalMetadataTransitions() // push metadata to cloud

// Rollback example
await tkey.generateNewShare()
tkey = await tkey.updateSDK() // this will revert the share generated above

Export and import shares as mnemonics

// Constructor
const 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();

// Constructor
const 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 back
await tkey.reconstructKey()

// get/set private keys
const privateKeys = [
new BN("4bd0041b7654a9b16a7268a5de7982f2422b15635c4fd170c140dc4897624390", "hex"),
new BN("1ea6edde61c750ec02896e9ac7fe9ac0b48a3630594fdf52ad5305470a2635c0", "hex"),
];
await tkey.modules.privateKeyModule.setPrivateKey("secp256k1n", privateKeys[0]);
await tkey.modules.privateKeyModule.getAccounts();

// get/set seedphrase
const 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.

// Constructor
const tkey = new ThresholdKey({
modules: {
[WEB_STORAGE_MODULE_NAME]: new WebStorageModule(),
},
serviceProvider,
storageLayer,
});

const resp1 = await tkey.initialize({}) // 2/2
const { newShareStores: newShareStores1, newShareIndex: newShareIndex1 } = await tkey.generateNewShare(); // 2/3
const { newShareStores: newShareStores2, newShareIndex: newShareIndex2 } = await tkey.generateNewShare(); // 2/4
const { newShareIndex: newShareIndex3 } = await tkey.generateNewShare(); // 2/5
await tkey.reconstructKey();

// Fetch existing share indexes
const 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);

// Reconstruction
const 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();