Sign on the Dotted Line!

Here at Bearmint, we love buildin’, buildin’, and, you guessed it - more buildin’! Seems like we might have somethin’ of an addiction to creatin’ workable solutions fer folks all over the world, and that’s all thanks to the good nature of our benevolent bear benefactor, Buckley!

So if you’ve been followin’ this here blog fer the last while, you’ll know that Buck’s had us put together a whole heap of guides to help you cut yer application development time down by a considerable margin. At the end of the day, Bearmint has all kinds of toolin’ you can use to speed things up a bit and get the ball rollin’ a little sooner!

Now we’ve covered transactions, addresses, key pairs, private keys and public keys, and we’ve even provided some information on how to go about contributin’ to the Bearmint cause. So now it’s time to learn how to build and implement signatures, or more specifically, BLS12-381 signatures.

Now why exactly are digital signatures needed? Well, to put it real simply, they provide a layer of security and ensure data integrity. Users need to sign different types of transactions, and this is exactly what their digital signature is for. Once they’ve signed a transaction, one or more external parties can verify the signature, therefore proving that the transaction originated from the user in question and that no data was altered after they signed it.

Yep, you just can’t create a blockchain without digital signatures! With this in mind, it’s time to learn how to build and implement signatures - but before we get to that, please take a few moments to review the followin’ definitions so you know what certain terms mean when you encounter them in the code! Let’s go!

Would You Be So Kind as to Clarify, Sir?

  • @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 truly amazin’ package by herumi. We highly recommend you take a good look at it should you need to work with BLS12-381 and care about performance and a minor dependency footprint.

Creating the Signature Entity

To begin, you’ll need to create a function that produces a signature. This function will accept hash, publicKey and signature arguments (each of which is a buffer).

function makeSignature({ hash, publicKey, signature }: SignatureStruct): Signature {
return {
// This is where we'll put all of our functions!
};
}

Converting to a Buffer

When performing tasks like verification, it’s important to use a buffer. This function converts the signature to said format.

function toBytes() {
return signature;
}

Converting to a String

Clients (like Wallets for example) might need to display the signatures of end-users. This particular function handles this by returning a human-readable value.

function toString() {
return bytesToHex(signature);
}

Verifying the Signature

When verifying messages or transactions, it’s important to ensure that the signature is valid. The following function handles this and should return a boolean to indicate the signature’s validity.

async function verify() {
try {
const blsSignature = new BLS.Signature();
blsSignature.deserialize(signature);
const blsPublicKey = new BLS.PublicKey();
blsPublicKey.deserialize(publicKey);
return blsPublicKey.verify(blsSignature, hash);
} catch {
return false;
}
}

Combining the Entity Functions

By combining all of the above, the following function, which returns a signature entity, is the result. It’s this entity that you’ll use in the signature factory (which we’ll build in just a moment).

function makeSignature({ hash, publicKey, signature }: SignatureStruct): Signature {
return {
toBytes() {
return signature;
},
toString() {
return bytesToHex(signature);
},
async verify() {
try {
const blsSignature = new BLS.Signature();
blsSignature.deserialize(signature);
const blsPublicKey = new BLS.PublicKey();
blsPublicKey.deserialize(publicKey);
return blsPublicKey.verify(blsSignature, hash);
} catch {
return false;
}
},
};
}

Creating a Signature From a Struct

When performing tasks like verification, you need to ensure that the signature is consistently processed. The following function takes the hash, public key and signature in order to create a signature entity.

async function from(arguments_) {
return makeSignature(arguments_);
}

Creating a Signature From a Hash and Key Pair

When signing a message, you can typically access the hash of said message as well as a key pair that you may use for signing. This function creates a cryptographic signature which then produces a signature entity that is returned.

async function sign({ hash, keyPair }) {
return makeSignature({
hash,
publicKey: keyPair.toPublicKey().toBytes(),
signature: arrayBufferToBytes(
makeSecretKey(keyPair.toPrivateKey().toBytes()).sign(hash).serialize(),
),
});
}

Combining the Factory Functions

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

function makeSignatureFactory(): SignatureFactory {
return {
async from(arguments_) {
return makeSignature(arguments_);
},
async sign({ hash, keyPair }) {
return makeSignature({
hash,
publicKey: keyPair.toPublicKey().toBytes(),
signature: arrayBufferToBytes(
makeSecretKey(keyPair.toPrivateKey().toBytes()).sign(hash).serialize(),
),
});
},
};
}

Combining the Entity and Factory

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

import { Signature, SignatureFactory, SignatureStruct } from '@bearmint/bep13';
import { arrayBufferToBytes, bytesToHex } from '@bearmint/bep19';
import BLS from 'bls-eth-wasm';
import { makeSecretKey } from './utils';
function makeSignature({ hash, publicKey, signature }: SignatureStruct): Signature {
return {
toBytes() {
return signature;
},
toString() {
return bytesToHex(signature);
},
async verify() {
try {
const blsSignature = new BLS.Signature();
blsSignature.deserialize(signature);
const blsPublicKey = new BLS.PublicKey();
blsPublicKey.deserialize(publicKey);
return blsPublicKey.verify(blsSignature, hash);
} catch {
return false;
}
},
};
}
function makeSignatureFactory(): SignatureFactory {
return {
async from(arguments_) {
return makeSignature(arguments_);
},
async sign({ hash, keyPair }) {
return makeSignature({
hash,
publicKey: keyPair.toPublicKey().toBytes(),
signature: arrayBufferToBytes(
makeSecretKey(keyPair.toPrivateKey().toBytes()).sign(hash).serialize(),
),
});
},
};
}

Registering With the Application

Finally, simply bind the newly-created factory function to the container as ContainerType.SignatureFactory. And that’s it! Bearmint will now use the created signature factory whenever it needs to interact with a signature.

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

That Oughtta Do It!

Yep, that’s really all there is to it, partners! You should now be able to build and implement them digital signatures like nobody’s business! Now we here at Bearmint know that no man (or woman fer that matter) is an island, so we don’t expect you to have to do everythin’ yerself! If you’ve got an issue or query you want answered, be sure to contact us so we can help you find the solutions yer lookin’ fer! We’re always happy to take on new challenges and solve complex problems, and if we can’t give you an answer straight off the bat, we’ll make sure to do our research and come back to you as soon as we figure somethin’ out!