Gettin' Them Key Pairs!

We gotta tell you Pilgrims, there’s only one thing better than a single key pair - a pair of key pairs! We’ve probably mentioned it before, but we here at Bearmint value security and do our best to ensure that we provide developers with the best possible measures to keep their applications safe from hacks and and vulnerabilities. Now it’s always a terrible thing fer validators if their key pair is compromised. Yep, if a consensus mechanism only assigns a single key pair to validators, there’s a major problem. Simply put, if their key pair falls into the wrong hands, there’s no way of salvagin’ the situation!

Fer this reason the best solution is to assign validators two key pairs, one fer forgin’ blocks and another fer authorizing on-chain tasks (like sendin’ a transaction fer instance). While this may be less convenient fer validators, it does mean they can take active steps to address the situation and mitigate any issues before any real damage is done! So although it’s a little bit less streamlined fer validators, we think it’s obvious that the trade-off is well worth it!

Of course it’s great to talk about key pairs and their possible uses, but if you can’t implement ‘em, well, that just ain’t no good! Fortunately Buckley had us put together this here guide to help you implement BLS12-381 key pairs. Yep, in just a few simple steps, you can have the key pairs you need to take care of yer validators and keep yer application safe and sound!

Now before proceedin’ we ask that you familiarize yerself with the followin’ - if you’ve read our previous blogs, you may already know what most of the definitions are. In that case, take a look once again and refresh yer memory!

Could You Put That in Layman’s Terms, Sheriff?

  • @bearmint/bep9 is an NPM package that offers an implementation of BEP-009 which contains a set of utilities to deal with particularly tedious tasks that do not fall within a single domain
  • @bearmint/bep13 is an NPM package that offers an implementation of BEP-013 which allows for the sharing of a set of types between all the modules that exist within Bearmint
  • @bearmint/bep16 is an NPM package that offers an implementation of BEP-016 which contains a standardized method of bootstrapping the plugins and application via service providers
  • @bearmint/bep19 is an NPM package that offers an implementation of BEP-019 which explains how protocol buffers will apply in order to provide a standard method of serialization and deserialization, and
  • bls-eth-wasm is an NPM package that offers an implementation of BLS12-381 cryptography
bls-eth-wasm is a real purdy package by herumi. We highly recommend you take a look-see if you need to work with BLS12-381 and care about performance and a minor dependency footprint.

Creating the Key Pair Entity

You’ll first need to create a function that generates a key pair. This particular function will accept privateKey and publicKey arguments, both of which fall under the category of buffers.

function makeKeyPair({
privateKey,
publicKey,
}: {
privateKey: Buffer;
publicKey: Buffer;
}): KeyPair {
// This is where we'll put all of our functions!
}

Converting to a Private Key

In order for Bearmint to provide guarantees in terms of consistency, it’s important to ensure that we always work with standardized entities. For clients, this typically concerns private keys - as such, the following function handles this by returning a private key entity.

toPrivateKey() {
return makePrivateKey(privateKey);
}

Converting to a Public Key

This function is much like the function for private keys, but in this case, it applies to public keys, thus returning a public key entity instead.

toPublicKey() {
return makePublicKey(publicKey);
}

Combining the Entity Functions

By combining all of the above code, the following function (which returns a key pair entity) results. It’s this entity that we’ll use in the key pair factory (which we’ll build in the upcoming steps).

function makeKeyPair({
privateKey,
publicKey,
}: {
privateKey: Buffer;
publicKey: Buffer;
}): KeyPair {
return {
toPrivateKey() {
return makePrivateKey(privateKey);
},
toPublicKey() {
return makePublicKey(publicKey);
},
};
}

Creating a Key Pair From a Private Key

Depending on the way a client works, an end-user may need to input their private key directly instead of a mnemonic. The following function handles this task.

function makeSecretKey(privateKey: Buffer): BLS.SecretKeyType {
const result = new BLS.SecretKey();
result.deserialize(privateKey);
return result;
}
async function fromPrivateKey(privateKey: Buffer) {
return makeKeyPair({
privateKey,
publicKey: arrayBufferToBytes(makeSecretKey(privateKey).getPublicKey().serialize()),
});
}

Creating a Key Pair From a Mnemonic

This acts in much the same way as the private key function, but in this case, a mnemonic applies (this is the most common method by which end-users authorize transactions).

async function fromMnemonic(mnemonic: string) {
return fromPrivateKey(arrayBufferToBytes(deriveKeyFromMnemonic(mnemonic)));
}

Combining the Factory Functions

By combining all of the above code, the following function (which will return a key pair entity based on different inputs) results.

function makeKeyPairFactory(): KeyPairFactory {
// eslint-disable-next-line unicorn/consistent-function-scoping
async function fromPrivateKey(privateKey: Buffer) {
return makeKeyPair({
privateKey,
publicKey: arrayBufferToBytes(makeSecretKey(privateKey).getPublicKey().serialize()),
});
}
return {
async fromMnemonic(mnemonic: string) {
return fromPrivateKey(arrayBufferToBytes(deriveKeyFromMnemonic(mnemonic)));
},
fromPrivateKey,
};
}

Combining the Entity and Factory

In the penultimate step, we combine our method of creating a key pair entity with the factory that allows us to create said entity from various sources to produce the following code.

import {
KeyPair,
KeyPairFactory,
MuSig,
PrivateKey,
PrivateKeyFactory,
PublicKey,
PublicKeyFactory,
} from '@bearmint/bep13';
import { arrayBufferToBytes, hexToBytes, hexToBytesMany } from '@bearmint/bep19';
import { deriveKeyFromMnemonic } from '@chainsafe/bls-keygen';
import BLS from 'bls-eth-wasm';
function makeSecretKey(privateKey: Buffer): BLS.SecretKeyType {
const result = new BLS.SecretKey();
result.deserialize(privateKey);
return result;
}
function makeKeyPair({
privateKey,
publicKey,
}: {
privateKey: Buffer;
publicKey: Buffer;
}): KeyPair {
return {
toPrivateKey() {
return makePrivateKey(privateKey);
},
toPublicKey() {
return makePublicKey(publicKey);
},
};
}
function makeKeyPairFactory(): KeyPairFactory {
// eslint-disable-next-line unicorn/consistent-function-scoping
async function fromPrivateKey(privateKey: Buffer) {
return makeKeyPair({
privateKey,
publicKey: arrayBufferToBytes(makeSecretKey(privateKey).getPublicKey().serialize()),
});
}
return {
async fromMnemonic(mnemonic: string) {
return fromPrivateKey(arrayBufferToBytes(deriveKeyFromMnemonic(mnemonic)));
},
fromPrivateKey,
};
}

Registering With the Application

And finally, to complete the process, we simply bind our newly-created factory function to the container as ContainerType.KeyPairFactory - Bearmint will now use our key pair factory whenever it needs to interact with a key pair.

import { ContainerType, Cradle, ServiceProvider } from '@bearmint/bep13';
import { makeServiceProviderSkeleton } from '@bearmint/bep16';
import BLS from 'bls-eth-wasm';
import { makeKeyPairFactory } from './keypair';
function makeServiceProvider(cradle: Cradle): ServiceProvider {
return {
...makeServiceProviderSkeleton(__dirname),
async register() {
cradle.Container.bind(ContainerType.KeyPairFactory).toFunctionSingleton(makeKeyPairFactory);
},
};
}

You’ve Made It, Partner! Congratulations!

That’s that amigo, you’ve successfully built yer own key pair package! However, if you still have any questions burnin’ in yer mind, don’t hesitate to contact us and let us know how we can help. We’re always happy to hear from our friends and fans, and you can be sure that Buckley and the Bearmint Gang will pitch in and provide a helpin’ hand whenever it’s needed!