Time Fer Some Public Keys!

So we’ve released more than a few guides now, so if you’ve been following our blog, you should have a good idea of how things work and how to set up some of the most important components fer yer application. Last time we covered how to build and implement the private keys that forms on part of a key pair. It therefore logically follows that you now need to take some time to familiarize yerself with buildin’ and implementin’ public keys!

Now unlike a private key, you can freely share yer public key with other network participants so you can send and receive funds accordingly. Fact is, a public key encrypts data so that it can’t be read by any bad actors, and someone uses the correspondin’ private key to decrypt that data and receive whatever’s bein’ sent to them, be it a message or tokens or what have you. It’s pretty standard cryptography, and while yer public key is important, you don’t have to fret about keepin’ it a secret. However, make sure yer private keys are always kept under lock and key so that they don’t get compromised - that’s disastrous to say the very least!

So now it’s time to find out how to build and set up public keys. As per usual, we ask that you first take a little time to review a few important definitions before you go and start workin’ through the guide - after all, if you don’t know what somethin’ means, the code’ll make little to no sense to you! That said, let’s dig in and learn a little about settin’ up them public keys!

Could You Refresh My Memory, Friend?

  • @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 nice package by herumi. We suggest you mosey on over and take a look at it if you need to work with BLS12-381 and care about performance and a minor dependency footprint.

Creating the Public Key Entity

You’ll first need to create a function that generates a public key. This particular function will accept a bytes argument, which is a buffer.

function makePublicKey(bytes: Buffer): PublicKey {
// This is where we'll put all of our functions!
}

Converting to a Buffer

When carrying out tasks like signing, you’ll need to use a buffer. This function converts the public key to this format.

function toBytes() {
return bytes;
}

Converting to a String

Clients (like Wallets for instance) may need to display the public keys of end-users. This particular function handles this by returning a human-readable value.

function toString() {
return bytes.toString('hex');
}

Verifying the Public Key

When verifying messages or transactions, it’s important to ensure that the public key is valid before carrying out more costly signature verification. The following function handles this and should return a boolean to indicate that the public key is valid.

async function verify() {
try {
const blsPublicKey = new BLS.PublicKey();
blsPublicKey.deserialize(bytes);
return true;
} catch {
return false;
}
}

Combining the Entity Functions

When we combine everything above, the following function, which returns a public key entity, is the result. It’s this entity that you’ll use in the public key factory (which we’ll build in a moment).

function makePublicKey(bytes: Buffer): PublicKey {
return {
toBytes() {
return bytes;
},
toString() {
return bytes.toString('hex');
},
async verify() {
try {
const blsPublicKey = new BLS.PublicKey();
blsPublicKey.deserialize(bytes);
return true;
} catch {
return false;
}
},
};
}

Creating a Public Key From a Buffer

Bearmint may use public keys internally, and it already has the ability to access the buffer that represents the public key. With this in mind, the following function allows it to create a public key instance for further processing with consistent behaviors.

async function fromBytes(bytes: Buffer) {
return makePublicKey(bytes);
}

Creating a Public Key From a MuSig Entity

Clients (like Wallets for example) may allow end-users to interact with MuSig wallets. In such cases, it’s necessary to derive a master public key from a MuSig struct. The following function handles this and returns a public key instance.

async function fromMuSig(muSig: MuSig) {
const aggregatedPublicKey = new BLS.PublicKey();
for (const publicKey of hexToBytesMany(muSig.slaves.map((publicKey) => publicKey))) {
const blsPublicKey = new BLS.PublicKey();
blsPublicKey.deserialize(publicKey);
aggregatedPublicKey.add(blsPublicKey);
}
return makePublicKey(arrayBufferToBytes(aggregatedPublicKey.serialize()));
}

Creating a Public Key From a String

Clients (like Wallets for example) may allow end-users to verify messages using their public key. The following function lets them to take a public key string and turn it into a public instance that will handle this verification.

async function fromString(publicKey: string) {
return makePublicKey(hexToBytes(publicKey));
}

Combining the Factory Functions

By combining all of the above, the following function, which returns a public key entity based on different inputs, is the result. The next step will combine all of our work and finally register it.

function makePublicKeyFactory(): PublicKeyFactory {
return {
async fromBytes(bytes: Buffer) {
return makePublicKey(bytes);
},
async fromMuSig(muSig: MuSig) {
const aggregatedPublicKey = new BLS.PublicKey();
for (const publicKey of hexToBytesMany(muSig.slaves.map((publicKey) => publicKey))) {
const blsPublicKey = new BLS.PublicKey();
blsPublicKey.deserialize(publicKey);
aggregatedPublicKey.add(blsPublicKey);
}
return makePublicKey(arrayBufferToBytes(aggregatedPublicKey.serialize()));
},
async fromString(publicKey: string) {
return makePublicKey(hexToBytes(publicKey));
},
};
}

Combining the Entity and Factory

In the second to last step, having created a way of generating a public key entity and factory that allows you to create that entity from various sources, the following code shows how all of this works together.

function makePublicKey(bytes: Buffer): PublicKey {
return {
toBytes() {
return bytes;
},
toString() {
return bytes.toString('hex');
},
async verify() {
try {
const blsPublicKey = new BLS.PublicKey();
blsPublicKey.deserialize(bytes);
return true;
} catch {
return false;
}
},
};
}
function makePublicKeyFactory(): PublicKeyFactory {
return {
async fromBytes(bytes: Buffer) {
return makePublicKey(bytes);
},
async fromMuSig(muSig: MuSig) {
const aggregatedPublicKey = new BLS.PublicKey();
for (const publicKey of hexToBytesMany(muSig.slaves.map((publicKey) => publicKey))) {
const blsPublicKey = new BLS.PublicKey();
blsPublicKey.deserialize(publicKey);
aggregatedPublicKey.add(blsPublicKey);
}
return makePublicKey(arrayBufferToBytes(aggregatedPublicKey.serialize()));
},
async fromString(publicKey: string) {
return makePublicKey(hexToBytes(publicKey));
},
};
}

Registering With the Application

Finally, to finish, simply bind the newly-created factory function to the container as ContainerType.PublicKeyFactory. And you’re all done! Bearmint will now use the created public key factory whenever it needs to interact with a public key.

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

Done and Dusted Y’All!

Well, that just about wraps up another guide, partners - you can now create yerself some public keys until the cows come home! And just remember that if you ever need any assistance or have any queries whatsoever, you can reach out to us on any of the appropriate channels, and we’ll come runnin’ to tend to yer concerns! Buckley prides himself on makin’ sure developers and fans of his work get the assistance they require, so we’re more than happy to provide it to you!