Latest 0.2.9
License Apache License 2.0
Platforms ios 10.0, watchos 2.0, tvos 10.0, osx 10.10

BLS Signatures implementation

Build Status


Implements BLS signatures with aggregation as in Boneh, Drijvers, Neven 2018, using
relic toolkit for cryptographic primitives (pairings, EC, hashing).
The BLS12-381 curve is used. The spec is here.


  • Non-interactive signature aggregation on identical or distinct messages
  • Aggregate aggregates (trees)
  • Efficient verification (only one pairing per distinct message)
  • Security against rogue public key attack
  • Aggregate public keys and private keys
  • M/N threshold keys and signatures using Joint-Feldman scheme
  • HD (BIP32) key derivation
  • Key and signature serialization
  • Batch verification
  • Signature division (divide an aggregate by a previously verified signature)
  • Python bindings
  • Pure python bls12-381 and signatures

Import the library


include "bls.hpp"

#### Creating keys and signatures
// Example seed, used to generate private key. Always use
// a secure RNG with sufficient entropy to generate a seed.
uint8_t seed[] = {0, 50, 6, 244, 24, 199, 1, 25, 52, 88, 192,
                  19, 18, 12, 89, 6, 220, 18, 102, 58, 209,
                  82, 12, 62, 89, 110, 182, 9, 44, 20, 254, 22};

bls::PrivateKey sk = bls::PrivateKey::FromSeed(seed, sizeof(seed));
bls::PublicKey pk = sk.GetPublicKey();

uint8_t msg[] = {100, 2, 254, 88, 90, 45, 23};

bls::Signature sig = sk.Sign(msg, sizeof(msg));

Serializing keys and signatures to bytes

uint8_t skBytes[bls::PrivateKey::PRIVATE_KEY_SIZE]; // 32 byte array
uint8_t pkBytes[bls::PublicKey::PUBLIC_KEY_SIZE]; // 48 byte array
uint8_t sigBytes[bls::Signature::SIGNATURE_SIZE]; // 96 byte array

sk.Serialize(skBytes); // 32 bytes
pk.Serialize(pkBytes); // 48 bytes
sig.Serialize(sigBytes); // 96 bytes

#### Loading keys and signatures from bytes
// Takes array of 32 bytes
sk = bls::PrivateKey::FromBytes(skBytes);

// Takes array of 48 bytes
pk = bls::PublicKey::FromBytes(pkBytes);

// Takes array of 96 bytes
sig = bls::Signature::FromBytes(sigBytes);

Verifying signatures

// Add information required for verification, to sig object
sig.SetAggregationInfo(bls::AggregationInfo::FromMsg(pk, msg, sizeof(msg)));

bool ok = sig.Verify();

#### Aggregate signatures for a single message
// Generate some more private keys
seed[0] = 1;
bls::PrivateKey sk1 = bls::PrivateKey::FromSeed(seed, sizeof(seed));
seed[0] = 2;
bls::PrivateKey sk2 = bls::PrivateKey::FromSeed(seed, sizeof(seed));

// Generate first sig
bls::PublicKey pk1 = sk1.GetPublicKey();
bls::Signature sig1 = sk1.Sign(msg, sizeof(msg));

// Generate second sig
bls::PublicKey pk2 = sk2.GetPublicKey();
bls::Signature sig2 = sk2.Sign(msg, sizeof(msg));

// Aggregate signatures together
vector<bls::Signature> sigs = {sig1, sig2};
bls::Signature aggSig = bls::Signature::Aggregate(sigs);

// For same message, public keys can be aggregated into one.
// The signature can be verified the same as a single signature,
// using this public key.
vector<bls::PublicKey> pubKeys = {pk1, pk2};
bls::PublicKey aggPubKey = bls::Signature::Aggregate(pubKeys);

Aggregate signatures for different messages

// Generate one more key and message
seed[0] = 3;
bls::PrivateKey sk3 = bls::PrivateKey::FromSeed(seed, sizeof(seed));
bls::PublicKey pk3 = sk3.GetPublicKey();
uint8_t msg2[] = {100, 2, 254, 88, 90, 45, 23};

// Generate the signatures, assuming we have 3 private keys
sig1 = sk1.Sign(msg, sizeof(msg));
sig2 = sk2.Sign(msg, sizeof(msg));
bls::Signature sig3 = sk3.Sign(msg2, sizeof(msg2));

// They can be noninteractively combined by anyone
// Aggregation below can also be done by the verifier, to
// make batch verification more efficient
vector sigsL = {sig1, sig2};
bls::Signature aggSigL = bls::Signature::Aggregate(sigsL);

// Arbitrary trees of aggregates
vector sigsFinal = {aggSigL, sig3};
bls::Signature aggSigFinal = bls::Signature::Aggregate(sigsFinal);

// Serialize the final signature

#### Verify aggregate signature for different messages
// Deserialize aggregate signature
aggSigFinal = bls::Signature::FromBytes(sigBytes);

// Create aggregation information (or deserialize it)
bls::AggregationInfo a1 = bls::AggregationInfo::FromMsg(pk1, msg, sizeof(msg));
bls::AggregationInfo a2 = bls::AggregationInfo::FromMsg(pk2, msg, sizeof(msg));
bls::AggregationInfo a3 = bls::AggregationInfo::FromMsg(pk3, msg2, sizeof(msg2));
vector<bls::AggregationInfo> infos = {a1, a2};
bls::AggregationInfo a1a2 = bls::AggregationInfo::MergeInfos(infos);
vector<bls::AggregationInfo> infos2 = {a1a2, a3};
bls::AggregationInfo aFinal = bls::AggregationInfo::MergeInfos(infos2);

// Verify final signature using the aggregation info
ok = aggSigFinal.Verify();

// If you previously verified a signature, you can also divide
// the aggregate signature by the signature you already verified.
ok = aggSigL.Verify();
vector<bls::Signature> cache = {aggSigL};
aggSigFinal = aggSigFinal.DivideBy(cache);

// Final verification is now more efficient
ok = aggSigFinal.Verify();

Aggregate private keys

vector privateKeysList = {sk1, sk2};
vector pubKeysList = {pk1, pk2};

// Create an aggregate private key, that can generate
// aggregate signatures
const bls::PrivateKey aggSk = bls::PrivateKey::Aggregate(
privateKeys, pubKeys);

bls::Signature aggSig3 = aggSk.Sign(msg, sizeof(msg));

#### HD keys
// Random seed, used to generate master extended private key
uint8_t seed[] = {1, 50, 6, 244, 24, 199, 1, 25, 52, 88, 192,
                  19, 18, 12, 89, 6, 220, 18, 102, 58, 209,
                  82, 12, 62, 89, 110, 182, 9, 44, 20, 254, 22};

bls::ExtendedPrivateKey esk = bls::ExtendedPrivateKey::FromSeed(
        seed, sizeof(seed));

bls::ExtendedPublicKey epk = esk.GetExtendedPublicKey();

// Use i >= 2^31 for hardened keys
bls::ExtendedPrivateKey skChild = esk.PrivateChild(0)

bls::ExtendedPublicKey pkChild = epk.PublicChild(0)

// Serialize extended keys
uint8_t buffer1[bls::ExtendedPublicKey::EXTENDED_PUBLIC_KEY_SIZE];   // 93 bytes
uint8_t buffer2[bls::ExtendedPrivateKey::EXTENDED_PRIVATE_KEY_SIZE]; // 77 bytes



Cmake, a c++ compiler, and python3 (for bindings) are required for building.

git submodule update --init --recursive
mkdir build
cd build
cmake ../
cmake --build . -- -j 6

Run tests


Run benchmarks


Link the library to use it

g++ -Wl,-no_pie  -Ibls-signatures/contrib/relic/include -Ibls-signatures/build/contrib/relic/incl
ude -Ibls-signatures/src/  -L./bls-signatures/build/ -l bls  yourfile.cpp

Notes on dependencies

Changes performed to relic: Added config files for Chia, and added gmp include in relic.h, new ep_map and ep2_map, new ep_pck and ep2_pck. Custom inversion function. Note: relic is used with the Apache 2.0 license.

Libsodium and GMP are optional dependencies: libsodium gives secure memory allocation,
and GMP speeds up the library by ~ 3x. To install them, either download them from github
and follow the instructions for each repo, or use a package manager like APT or brew.


Discussion about this library and other Chia related development is on Keybase.
Install Keybase, and run the following to join the Chia public channels:

keybase team request-access chia_network.public

Code style

  • Always use uint8_t for bytes
  • Use size_t for size variables
  • Uppercase method names
  • Prefer static constructors
  • Avoid using templates
  • Objects allocate and free their own memory
  • Use cpplint with default rules


  • Serialize aggregation info
  • Secure allocation during signing, key derivation
  • Remove unnecessary dependency files
  • Constant time and side channel attacks
  • Adaptor signatures / Blind signatures
  • More tests vectors (failed verifications, etc)

Specification and test vectors

The specification and test vectors can be found here. Test vectors can also be seen in the python or cpp test files.

Latest podspec

    "name": "bls-signatures-pod",
    "version": "0.2.9",
    "summary": "BLS signatures in C++, using the relic toolkit",
    "description": "Implements BLS signatures with aggregation as in Boneh, Drijvers, Neven 2018, using relic toolkit for cryptographic primitives (pairings, EC, hashing). The BLS12-381 curve is used.",
    "homepage": "",
    "license": {
        "type": "Apache License 2.0"
    "authors": {
        "Chia Network": "[email protected]"
    "social_media_url": "",
    "source": {
        "git": "",
        "commit": "f114ffeff4653e5522d1b3e28687fa9f384a557f",
        "submodules": true
    "prepare_command": "set -xnn    MIN_IOS="10.0"n    MIN_WATCHOS="2.0"n    MIN_TVOS=$MIN_IOSn    MIN_MACOS="10.10"nn    IPHONEOS=iphoneosn    IPHONESIMULATOR=iphonesimulatorn    WATCHOS=watchosn    WATCHSIMULATOR=watchsimulatorn    TVOS=appletvosn    TVSIMULATOR=appletvsimulatorn    MACOS=macosxnn    LOGICALCPU_MAX=`sysctl -n hw.logicalcpu_max`nn    GMP_DIR="`pwd`/gmp"nn    version_min_flag()n    {n        PLATFORM=$1nn        FLAG=""n        if [[ $PLATFORM = $IPHONEOS ]]; thenn            FLAG="-miphoneos-version-min=${MIN_IOS}"n        elif [[ $PLATFORM = $IPHONESIMULATOR ]]; thenn            FLAG="-mios-simulator-version-min=${MIN_IOS}"n        elif [[ $PLATFORM = $WATCHOS ]]; thenn            FLAG="-mwatchos-version-min=${MIN_WATCHOS}"n        elif [[ $PLATFORM = $WATCHSIMULATOR ]]; thenn            FLAG="-mwatchos-simulator-version-min=${MIN_WATCHOS}"n        elif [[ $PLATFORM = $TVOS ]]; thenn            FLAG="-mtvos-version-min=${MIN_TVOS}"n        elif [[ $PLATFORM = $TVSIMULATOR ]]; thenn            FLAG="-mtvos-simulator-version-min=${MIN_TVOS}"n        elif [[ $PLATFORM = $MACOS ]]; thenn            FLAG="-mmacosx-version-min=${MIN_MACOS}"n        finn        echo $FLAGn    }nnn    prepare()n    {n        download_gmp()n        {n            GMP_VERSION="6.1.2"n            CURRENT_DIR=`pwd`nn            if [ ! -s ${CURRENT_DIR}/gmp-${GMP_VERSION}.tar.bz2 ]; thenn                curl -L -o ${CURRENT_DIR}/gmp-${GMP_VERSION}.tar.bz2${GMP_VERSION}.tar.bz2n            finn            rm -rf gmpn            tar xfj "gmp-${GMP_VERSION}.tar.bz2"n            mv gmp-${GMP_VERSION} gmpn        }nn        download_cmake_toolchain()n        {n            pushd contrib/relicnn            if [ ! -s ios.toolchain.cmake ]; thenn                SHA256_HASH="782853957073f8e7cfa21c94823c74519eaf75c93960f13fcef44cd9ec6eb10e"n                curl -o ios.toolchain.cmake                DOWNLOADED_HASH=`shasum -a 256 ios.toolchain.cmake | cut -f 1 -d " "`n                if [ $SHA256_HASH != $DOWNLOADED_HASH ]; thenn                  echo "Error: sha256 checksum of ios.toolchain.cmake mismatch" >&2n                  exit 1n                fin            finn            popd # contrib/relicn        }nn        download_gmpn        download_cmake_toolchainnn        rm -rf artefactsn        mkdir artefactsn    }nnn    build_gmp()n    {n        build_gmp_arch()n        {n            PLATFORM=$1n            ARCH=$2nn            SDK=`xcrun --sdk $PLATFORM --show-sdk-path`n            PLATFORM_PATH=`xcrun --sdk $PLATFORM --show-sdk-platform-path`n            CLANG=`xcrun --sdk $PLATFORM --find clang`n            CURRENT_DIR=`pwd`n            DEVELOPER=`xcode-select --print-path`n            export PATH="${PLATFORM_PATH}/Developer/usr/bin:${DEVELOPER}/usr/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin"nn            make clean || truen            make distclean || truenn            mkdir gmplib-${PLATFORM}-${ARCH}nn            CFLAGS="-fembed-bitcode -arch ${ARCH} --sysroot=${SDK}"n            EXTRA_FLAGS="$(version_min_flag $PLATFORM)"nn            CCARGS="${CLANG} ${CFLAGS}"n            CPPFLAGSARGS="${CFLAGS} ${EXTRA_FLAGS}"nn            CONFIGURESCRIPT=""nn            cat >"$CONFIGURESCRIPT" << EOFn#!/bin/shnn./configure CC="$CCARGS" CPPFLAGS="$CPPFLAGSARGS" --disable-shared --enable-static --host=arm-apple-darwin --disable-assembly --prefix="${CURRENT_DIR}/gmplib-${PLATFORM}-${ARCH}"nnEOFnn            chmod a+x "$CONFIGURESCRIPT"n            sh "$CONFIGURESCRIPT"n            rm "$CONFIGURESCRIPT"nn            make -j $LOGICALCPU_MAX &> "${CURRENT_DIR}/gmplib-${PLATFORM}-${ARCH}-build.log"n            make install &> "${CURRENT_DIR}/gmplib-${PLATFORM}-${ARCH}-install.log"n        }nn        BUILD_IN=$1n        IFS='|' read -ra BUILD_PAIRS <<< "$BUILD_IN"nn        pushd gmpnn        LIPOARGS=""n        PLATFORM=""n        ARCH=""n        for BUILD_PAIR in "${BUILD_PAIRS[@]}"n        don            IFS=';' read -ra PARSED_PAIR <<< "$BUILD_PAIR"n            PLATFORM=${PARSED_PAIR[0]}n            ARCH=${PARSED_PAIR[1]}nn            build_gmp_arch $PLATFORM $ARCHn            LIPOARGS+="gmplib-${PLATFORM}-${ARCH}/lib/libgmp.a "n        donenn        cp -r gmplib-${PLATFORM}-${ARCH}/include .nn        rm -rf libn        mkdir libnn        xcrun lipo $LIPOARGS -create -output lib/libgmp.ann        popd # gmpn    }nnn    build_relic()n    {n        build_relic_arch()n        {n            PLATFORM=$1n            ARCH=$2nn            SDK=`xcrun --sdk $PLATFORM --show-sdk-path`nn            BUILDDIR="relic-${PLATFORM}-${ARCH}"n            rm -rf $BUILDDIRn            mkdir $BUILDDIRn            pushd $BUILDDIRnn            unset CCn            export CC=`xcrun --sdk ${PLATFORM} --find clang`nn            WSIZE=0n            IOS_PLATFORM=""n            OPTIMIZATIONFLAGS=""nn            if [[ $PLATFORM = $IPHONEOS ]]; thenn                if [[ $ARCH = "arm64" ]] || [[ $ARCH = "arm64e" ]]; thenn                    IOS_PLATFORM=OS64n                    WSIZE=64n                    OPTIMIZATIONFLAGS=-fomit-frame-pointern                elsen                    IOS_PLATFORM=OSn                    WSIZE=32n                fin            elif [[ $PLATFORM = $IPHONESIMULATOR ]]; thenn                if [[ $ARCH = "x86_64" ]]; thenn                    IOS_PLATFORM=SIMULATOR64n                    WSIZE=64n                    OPTIMIZATIONFLAGS=-fomit-frame-pointern                elsen                    IOS_PLATFORM=SIMULATORn                    WSIZE=32n                fin            elif [[ $PLATFORM = $WATCHOS ]]; thenn                IOS_PLATFORM=WATCHOSn                WSIZE=32n            elif [[ $PLATFORM = $WATCHSIMULATOR ]]; thenn                IOS_PLATFORM=SIMULATOR_WATCHOSn                WSIZE=32n            elif [[ $PLATFORM = $TVOS ]]; thenn                IOS_PLATFORM=TVOSn                WSIZE=64n                OPTIMIZATIONFLAGS=-fomit-frame-pointern            elif [[ $PLATFORM = $TVSIMULATOR ]]; thenn                IOS_PLATFORM=SIMULATOR_TVOSn                WSIZE=64n                OPTIMIZATIONFLAGS=-fomit-frame-pointern            elif [[ $PLATFORM = $MACOS ]]; thenn                WSIZE=64n                OPTIMIZATIONFLAGS=-fomit-frame-pointern            fin            n            COMPILER_ARGS=""n            if [[ $ARCH != "i386" ]]; thenn                COMPILER_ARGS=$(version_min_flag $PLATFORM)n            fin            n            EXTRA_ARGS=""n            if [[ $PLATFORM = $MACOS ]]; thenn                EXTRA_ARGS="-DOPSYS=MACOSX"    n            elsen                EXTRA_ARGS="-DOPSYS=NONE -DIOS_PLATFORM=$IOS_PLATFORM -DCMAKE_TOOLCHAIN_FILE=../ios.toolchain.cmake"n            fin            n            if [[ $ARCH = "i386" ]]; thenn                EXTRA_ARGS+=" -DARCH=X86"n            elif [[ $ARCH = "x86_64" ]]; thenn                EXTRA_ARGS+=" -DARCH=X64"n            elsen                EXTRA_ARGS+=" -DARCH=ARM"n                if [[ $ARCH = "armv7s" ]]; thenn                    EXTRA_ARGS+=" -DIOS_ARCH=armv7s"n                elif [[ $ARCH = "armv7k" ]]; thenn                    EXTRA_ARGS+=" -DIOS_ARCH=armv7k"n                elif [[ $ARCH = "arm64_32" ]]; thenn                    EXTRA_ARGS+=" -DIOS_ARCH=arm64_32"n                fin            finn            CURRENT_DIR=`pwd`nn            cmake -DCMAKE_PREFIX_PATH:PATH="${GMP_DIR}" -DTESTS=0 -DBENCH=0 -DCHECK=off -DARITH=gmp -DFP_PRIME=381 -DMULTI=PTHREAD             -DFP_QNRES=off -DFP_METHD="INTEG;INTEG;INTEG;MONTY;LOWER;SLIDE" -DFPX_METHD="INTEG;INTEG;LAZYR" -DPP_METHD="LAZYR;OATEP"             -DCOMP="-O3 -funroll-loops $OPTIMIZATIONFLAGS -isysroot $SDK -arch $ARCH -fembed-bitcode ${COMPILER_ARGS}" -DWSIZE=$WSIZE             -DVERBS=off -DSHLIB=off -DALLOC="AUTO" -DEP_PLAIN=off -DEP_SUPER=off -DPP_EXT="LAZYR" -DTIMER="HREAL" ${EXTRA_ARGS} ../nn            make -j $LOGICALCPU_MAXnn            popd # "$BUILDDIR"n        }nn        BUILD_IN=$1n        IFS='|' read -ra BUILD_PAIRS <<< "$BUILD_IN"n        n        pushd contrib/relicnn        LIPOARGS=""n        for BUILD_PAIR in "${BUILD_PAIRS[@]}"n        don            IFS=';' read -ra PARSED_PAIR <<< "$BUILD_PAIR"n            PLATFORM=${PARSED_PAIR[0]}n            ARCH=${PARSED_PAIR[1]}nn            build_relic_arch $PLATFORM $ARCHn            LIPOARGS+="relic-${PLATFORM}-${ARCH}/lib/librelic_s.a "n        donenn        xcrun lipo $LIPOARGS -create -output librelic.ann        popd # contrib/relicn    }nnn    build_bls()n    {n        BLS_FILES=( "aggregationinfo" "bls" "chaincode" "extendedprivatekey" "extendedpublickey" "privatekey" "publickey" "signature" )n        ALL_BLS_OBJ_FILES=$(printf "%s.o " "${BLS_FILES[@]}")nn        build_bls_arch()n        {n            PLATFORM=$1n            ARCH=$2nn            SDK=`xcrun --sdk $PLATFORM --show-sdk-path`nn            BUILDDIR="bls-${PLATFORM}-${ARCH}"n            rm -rf $BUILDDIRn            mkdir $BUILDDIRn            pushd $BUILDDIRnn            EXTRA_ARGS="$(version_min_flag $PLATFORM)"nn            CURRENT_DIR=`pwd`nn            for F in "${BLS_FILES[@]}"n            don                clang -I"../contrib/relic/include" -I"../contrib/relic/relic-${PLATFORM}-${ARCH}/include" -I"../src/" -I"${GMP_DIR}/include"                 -x c++ -std=c++14 -stdlib=libc++ -fembed-bitcode -arch "${ARCH}" -isysroot "${SDK}" ${EXTRA_ARGS} -c "../src/${F}.cpp" -o "${CURRENT_DIR}/${F}.o"n            donenn            ar -cvq libbls.a $ALL_BLS_OBJ_FILESnn            popd # "$BUILDDIR"n        }nn        BUILD_IN=$1n        IFS='|' read -ra BUILD_PAIRS <<< "$BUILD_IN"nn        LIPOARGS=""n        for BUILD_PAIR in "${BUILD_PAIRS[@]}"n        don            IFS=';' read -ra PARSED_PAIR <<< "$BUILD_PAIR"n            PLATFORM=${PARSED_PAIR[0]}n            ARCH=${PARSED_PAIR[1]}nn            build_bls_arch $PLATFORM $ARCHn            LIPOARGS+="bls-${PLATFORM}-${ARCH}/libbls.a "n        donenn        xcrun lipo $LIPOARGS -create -output    }nnn    build_all()n    {n        SUFFIX=$1n        BUILD_IN=$2nn        build_gmp $BUILD_INn        build_relic $BUILD_INn        build_bls $BUILD_INnn        mv gmp/lib/libgmp.a "artefacts/libgmp_${SUFFIX}.a"n        mv contrib/relic/librelic.a "artefacts/librelic_${SUFFIX}.a"n        mv libbls.a "artefacts/libbls_${SUFFIX}.a"n    }nnn    make_relic_universal()n    {n        RELIC_TARGET_DIR=relic-iphoneos-arm64nn        perl -p -e 's/#define WSIZE.*/#ifdef __LP64__n#define WSIZE 64n#elsen#define WSIZE 32n#endif/'         "contrib/relic/${RELIC_TARGET_DIR}/include/relic_conf.h"         > "contrib/relic/${RELIC_TARGET_DIR}/include/"nn        rm "contrib/relic/${RELIC_TARGET_DIR}/include/relic_conf.h"n        mv "contrib/relic/${RELIC_TARGET_DIR}/include/" "contrib/relic/${RELIC_TARGET_DIR}/include/relic_conf.h"   n    }nn    preparenn    build_all "macos" "${MACOS};x86_64"n    build_all "watchos" "${WATCHOS};armv7k|${WATCHOS};arm64_32|${WATCHSIMULATOR};i386"n    build_all "tvos" "${TVOS};arm64|${TVSIMULATOR};x86_64"n    build_all "ios" "${IPHONEOS};arm64|${IPHONESIMULATOR};i386|${IPHONESIMULATOR};x86_64"nn    make_relic_universal",
    "platforms": {
        "ios": "10.0",
        "watchos": "2.0",
        "tvos": "10.0",
        "osx": "10.10"
    "libraries": "c++",
    "pod_target_xcconfig": {
        "GCC_WARN_64_TO_32_BIT_CONVERSION": "NO",
    "source_files": [
    "exclude_files": "src/test-utils.hpp",
    "ios": {
        "vendored_libraries": [
    "watchos": {
        "vendored_libraries": [
    "tvos": {
        "vendored_libraries": [
    "osx": {
        "vendored_libraries": [

Pin It on Pinterest

Share This