Vault

Use Case: Hand Holding without Key Holding

Our customers need to sign transactions with a cryptographic key. We do not want to be in possession of this key. But our customers can not easily be trained to manage keys themselves. We need the keys to be managed for them, without us holding the keys.

To accomplish this, we will give each user a vault that will hold their keys and sign their transactions. The user will authorize an API access token for each transaction, that will temporarily allow us to request that the vault sign the transaction with the key. The token will be scoped to the specific transaction to be signed.

What tools are involved?

Creating our own identity

Before we start helping others with identities and vaults, we need our own. We’ll need a user, an organization, and an application. We can do this through the Ident API, but it is more convenient to use the CLI. Follow the CLI instructions in Prerequisites and Installing from Source before continuing. You can run prvd --help to verify the CLI is installed and to see what all it can do.

Create a user

$ prvd users init

The CLI conveniently prompts for the required input: First Name, Last Name, Email, Password.

First Name: Philip
Last Name: Keiter
Email: philip@example.com
Password: ********

After filing these in, the CLI returns the new User ID.

created user: 92e9804f-0639-4d61-9ffa-c1d0434089b1

Create an organization

$ prvd organizations init

The CLI conveniently prompts for the required input: Organization Name.

Organization Name: Philip's Org

After filing that in, the CLI returns the new Organization ID.

initialized organization: Philip's Org      aa79cc1c-300b-4814-aa4f-be9ce918c9ce

Create an application

$ prvd applications init --name 'My App' --network 024ff1ef-7369-4dee-969c-1918c6edb5d4 --without-wallet

The CLI returns the application ID, name, account, and address.

63b9faad-2269-4298-90e6-9281e6437a54 My App
Account adc02bbb-98ac-4011-8bdf-b12ab5150b22	0x69C147D1C8B84f87002438b71B8A681624d3E620

You can create your application on any network. prvd networks list will show your private networks and prvd networks list --public will show available public networks.


Create an application API token

$ prvd api_tokens init --application '63b9faad-2269-4298-90e6-9281e6437a54' --offline-access

The CLI returns an access token and refresh token.

Access token authorized for application: 63b9faad-2269-4298-90e6-9281e6447a54 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Refresh token authorized for application: 63b9faad-2269-4298-90e6-9281e6447a54 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Creating customer identities

Now that we have our own identity, we can create identities for our customers. We have a lot of customers, so we will do this in an automated way using scripts. PRVD has helper libraries for many programming languages to more conveniently use the API. JavaScript is a popular language and can be used in either a front end proof of concept, or a back end NodeJS server.

Import the client factories

import { identClientFactory, vaultClientFactory } from 'provide-js';

Set the account ID, application ID, and access token

const accountId = 'adc02bbb-98ac-4011-8bdf-b12ab5150b22';
const appId = '63b9faad-2269-4298-90e6-9281e6437a54';
const appAccessToken = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx';

Initialize an Ident client instance

const ident = identClientFactory(appAccessToken);

Create a virtual user

const virtualUser = await ident.createUser({
  application_id: appId,
  email: `joe.user.${new Date().getTime()}@example.com`,
  first_name: 'Joe',
  last_name: 'User',
})

console.log(virtualUser.id);

The user ID should be persisted and associated with your record of your user.

Create a user access token

const userAccessToken = (await ident.createToken({
  application_id: appId,
  user_id: virtualUser.id,
  scope: 'offline_access',
})).accessToken;

The refresh token is also returned. It should be persisted and used to create future access tokens.

Create an organization

const org = await ident.createOrganization({
  name: 'Acme Inc.',
});

console.log(org.id);

The organization ID should be persisted and associated with your record of your user.

Create an organization access token

const orgAccessToken = (await ident.createToken({
  organization_id: org.id,
  scope: 'offline_access',
})).accessToken;

The refresh token is also returned. It should be persisted and used to create future access tokens.

Initialize a Vault client instance

const vault = vaultClientFactory(orgAccessToken);

Create a vault

const orgVault = await vault.createVault({
  name: `${org.name} Vault`,
  description: `${org.name} vault instance`,
});

console.log(orgVault.id);

The vault ID should be persisted and associated with your record of your user.

Create a key

const key = await vault.createVaultKey(orgVault.id, {
  type: 'asymmetric',
  usage: 'sign/verify',
  spec: 'secp256k1',
  name: `${org.name} ETH keypair`,
  description: `${org.name} ETH keypair`,
});

console.log(key.id);

The key ID should be persisted and associated with your record of your user.

Creating and signing transactions

Create a transaction

Use your preferred library of choice to construct a raw Ethereum transaction. To sign it, Vault will need its transaction hash.

Format transaction hash

If the transaction hash starts with "0x" we need to trim that off.

const hash = (transactionHash.substring(0, 2) === '0x')
  ? transaction.hash.substring(2)
  : transaction.hash;

Sign a transaction

const signedMessage = await vault.signMessage(orgVault.id, key.id, hash);

console.log(signedMessage.signature);

Mission Accomplished

As you can see, we have the user ID, organization ID, vault ID, key ID, and an access token, but we do not have the actual key. It is stored securely in the vault. Vault signs the message in a custodial or non-custodial manner, depending on if you (i) are relying on the commercially-supported managed Provide vault (custodial) or (ii) deployed Vault to your own infrastructure (non-custodial).

Last updated