@chainsafe/bls
Version:
Implementation of bls signature verification for ethereum 2.0
209 lines (208 loc) • 8.18 kB
JavaScript
import { validateBytes } from "./helpers/index.js";
import { NotInitializedError } from "./errors.js";
/**
* NOTE:
*
* This conditional block below is present in most functions and is Herumi specific to prevent
* downstream issues in the WASM code. It is not necessary to validateBytes for blst-native
* code. The check for blst-native is implemented in the native layer. All other byte checks
* for the herumi code paths are found in the herumi classes for performance reasons as they
* are byte-wise, only are required for the WASM and will unnecessarily slow down the
* blst-native side.
*/
// if (implementation === "herumi" && signature instanceof Uint8Array) {
// validateBytes(signature, "signature");
// }
// Returned type is enforced at each implementation's index
// eslint-disable-next-line max-len
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type,@typescript-eslint/explicit-module-boundary-types
export function functionalInterfaceFactory({ implementation, SecretKey, PublicKey, Signature, }) {
/**
* Signs given message using secret key.
*/
function sign(secretKey, message) {
validateBytes(secretKey, "secretKey");
validateBytes(message, "message");
return SecretKey.fromBytes(secretKey).sign(message).toBytes();
}
/**
* Compines all given signature into one.
*/
function aggregateSignatures(signatures) {
return Signature.aggregate(signatures).toBytes();
}
/**
* Combines all given public keys into single one
*/
function aggregatePublicKeys(publicKeys) {
return PublicKey.aggregate(publicKeys).toBytes();
}
/**
* Verifies if signature is message signed with given public key.
*/
function verify(publicKey, message, signature) {
try {
if (implementation === "herumi" && signature instanceof Uint8Array) {
validateBytes(signature, "signature");
}
const sig = signature instanceof Signature ? signature : Signature.fromBytes(signature);
return sig.verify(publicKey, message);
}
catch (e) {
if (e instanceof NotInitializedError)
throw e;
return false;
}
}
/**
* Verifies if aggregated signature is same message signed with given public keys.
*/
function verifyAggregate(publicKeys, message, signature) {
try {
if (implementation === "herumi" && signature instanceof Uint8Array) {
validateBytes(signature, "signature");
}
const sig = signature instanceof Signature ? signature : Signature.fromBytes(signature);
return sig.verifyAggregate(publicKeys, message);
}
catch (e) {
if (e instanceof NotInitializedError)
throw e;
return false;
}
}
/**
* Verifies if signature is list of message signed with corresponding public key.
*/
function verifyMultiple(publicKeys, messages, signature) {
// TODO: (@matthewkeil) blst-ts has this check but throws an error instead of returning false. Moving to
// the herumi and commenting here for now. Will double check spec on what is appropriate.
// if (publicKeys.length === 0 || publicKeys.length != messages.length) {
// return false;
// }
try {
if (implementation === "herumi" && signature instanceof Uint8Array) {
validateBytes(signature, "signature");
}
const sig = signature instanceof Signature ? signature : Signature.fromBytes(signature);
return sig.verifyMultiple(publicKeys, messages);
}
catch (e) {
if (e instanceof NotInitializedError)
throw e;
return false;
}
}
/**
* Verifies multiple signatures at once returning true if all valid or false
* if at least one is not. Optimization useful when knowing which signature is
* wrong is not relevant, i.e. verifying an entire Eth2.0 block.
*
* This method provides a safe way to do so by multiplying each signature by
* a random number so an attacker cannot craft a malicious signature that won't
* verify on its own but will if it's added to a specific predictable signature
* https://ethresear.ch/t/fast-verification-of-multiple-bls-signatures/5407
*/
function verifyMultipleSignatures(sets) {
try {
return Signature.verifyMultipleSignatures(sets);
}
catch (e) {
if (e instanceof NotInitializedError)
throw e;
return false;
}
}
/**
* Verifies if signature is message signed with given public key.
*/
async function asyncVerify(publicKey, message, signature) {
if (implementation === "herumi")
return verify(publicKey, message, signature);
try {
// must be in try/catch in case sig is invalid
const sig = signature instanceof Signature ? signature : Signature.fromBytes(signature);
return await sig.asyncVerify(publicKey, message);
}
catch (e) {
if (e instanceof NotInitializedError)
throw e;
return false;
}
}
/**
* Verifies if aggregated signature is same message signed with given public keys.
*/
async function asyncVerifyAggregate(publicKeys, message, signature) {
if (implementation === "herumi")
return verifyAggregate(publicKeys, message, signature);
try {
const sig = signature instanceof Signature ? signature : Signature.fromBytes(signature);
return await sig.asyncVerifyAggregate(publicKeys, message);
}
catch (e) {
if (e instanceof NotInitializedError)
throw e;
return false;
}
}
/**
* Verifies if signature is list of message signed with corresponding public key.
*/
async function asyncVerifyMultiple(publicKeys, messages, signature) {
if (implementation === "herumi")
return verifyMultiple(publicKeys, messages, signature);
try {
const sig = signature instanceof Signature ? signature : Signature.fromBytes(signature);
return await sig.asyncVerifyMultiple(publicKeys, messages);
}
catch (e) {
if (e instanceof NotInitializedError)
throw e;
return false;
}
}
/**
* Verifies multiple signatures at once returning true if all valid or false
* if at least one is not. Optimization useful when knowing which signature is
* wrong is not relevant, i.e. verifying an entire Eth2.0 block.
*
* This method provides a safe way to do so by multiplying each signature by
* a random number so an attacker cannot craft a malicious signature that won't
* verify on its own but will if it's added to a specific predictable signature
* https://ethresear.ch/t/fast-verification-of-multiple-bls-signatures/5407
*/
async function asyncVerifyMultipleSignatures(sets) {
try {
if (implementation === "herumi")
return Signature.verifyMultipleSignatures(sets);
return await Signature.asyncVerifyMultipleSignatures(sets);
}
catch (e) {
if (e instanceof NotInitializedError)
throw e;
return false;
}
}
/**
* Computes a public key from a secret key
*/
function secretKeyToPublicKey(secretKey) {
validateBytes(secretKey, "secretKey");
return SecretKey.fromBytes(secretKey).toPublicKey().toBytes();
}
return {
sign,
aggregateSignatures,
aggregatePublicKeys,
verify,
asyncVerify,
verifyAggregate,
asyncVerifyAggregate,
verifyMultiple,
asyncVerifyMultiple,
verifyMultipleSignatures,
asyncVerifyMultipleSignatures,
secretKeyToPublicKey,
};
}