BLS Signatures implementation



NOTE: THIS LIBRARY IS NOT YET FORMALLY REVIEWED FOR SECURITY
NOTE: THIS LIBRARY WAS SHIFTED TO THE IETF BLS SPECIFICATION ON 7/16/20
Implements BLS signatures with aggregation using blst library
for cryptographic primitives (pairings, EC, hashing) according to the
IETF BLS RFC
with these curve parameters
for BLS12-381. The blst library has been audited.
Features:
- Non-interactive signature aggregation following IETF specification
- Works on Windows, Mac, Linux, BSD, arm64, RISC-V
- Efficient verification using Proof of Posssesion (only one pairing per distinct message)
- Aggregate public keys and private keys
- EIP-2333 key derivation (including unhardened BIP-32-like keys)
- Key and signature serialization
- Batch verification
- Python bindings
- Pure python bls12-381 and signatures
- JavaScript bindings
Before you start
This library uses minimum public key sizes (MPL). A G2Element is a signature (96 bytes), and a G1Element is a public key (48 bytes). A private key is a 32 byte integer. There are three schemes: Basic, Augmented, and ProofOfPossession. Augmented should be enough for most use cases, and ProofOfPossession can be used where verification must be fast.
Import the library
#include "bls.hpp"
using namespace bls;
Creating keys and signatures
vector<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};
PrivateKey sk = AugSchemeMPL().KeyGen(seed);
G1Element pk = sk.GetG1Element();
vector<uint8_t> message = {1, 2, 3, 4, 5};
G2Element signature = AugSchemeMPL().Sign(sk, message);
bool ok = AugSchemeMPL().Verify(pk, message, signature);
Serializing keys and signatures to bytes
vector<uint8_t> skBytes = sk.Serialize();
vector<uint8_t> pkBytes = pk.Serialize();
vector<uint8_t> signatureBytes = signature.Serialize();
cout << Util::HexStr(skBytes) << endl;
cout << Util::HexStr(pkBytes) << endl;
cout << Util::HexStr(signatureBytes) << endl;
Loading keys and signatures from bytes
PrivateKey skc = PrivateKey::FromByteVector(skBytes);
pk = G1Element::FromByteVector(pkBytes);
signature = G2Element::FromByteVector(signatureBytes);
Create aggregate signatures
seed[0] = 1;
PrivateKey sk1 = AugSchemeMPL().KeyGen(seed);
seed[0] = 2;
PrivateKey sk2 = AugSchemeMPL().KeyGen(seed);
vector<uint8_t> message2 = {1, 2, 3, 4, 5, 6, 7};
G1Element pk1 = sk1.GetG1Element();
G2Element sig1 = AugSchemeMPL().Sign(sk1, message);
G1Element pk2 = sk2.GetG1Element();
G2Element sig2 = AugSchemeMPL().Sign(sk2, message2);
G2Element aggSig = AugSchemeMPL().Aggregate({sig1, sig2});
ok = AugSchemeMPL().AggregateVerify({pk1, pk2}, {message, message2}, aggSig);
Arbitrary trees of aggregates
seed[0] = 3;
PrivateKey sk3 = AugSchemeMPL().KeyGen(seed);
G1Element pk3 = sk3.GetG1Element();
vector<uint8_t> message3 = {100, 2, 254, 88, 90, 45, 23};
G2Element sig3 = AugSchemeMPL().Sign(sk3, message3);
G2Element aggSigFinal = AugSchemeMPL().Aggregate({aggSig, sig3});
ok = AugSchemeMPL().AggregateVerify({pk1, pk2, pk3}, {message, message2, message3}, aggSigFinal);
Very fast verification with Proof of Possession scheme
G2Element popSig1 = PopSchemeMPL().Sign(sk1, message);
G2Element popSig2 = PopSchemeMPL().Sign(sk2, message);
G2Element popSig3 = PopSchemeMPL().Sign(sk3, message);
G2Element pop1 = PopSchemeMPL().PopProve(sk1);
G2Element pop2 = PopSchemeMPL().PopProve(sk2);
G2Element pop3 = PopSchemeMPL().PopProve(sk3);
ok = PopSchemeMPL().PopVerify(pk1, pop1);
ok = PopSchemeMPL().PopVerify(pk2, pop2);
ok = PopSchemeMPL().PopVerify(pk3, pop3);
G2Element popSigAgg = PopSchemeMPL().Aggregate({popSig1, popSig2, popSig3});
ok = PopSchemeMPL().FastAggregateVerify({pk1, pk2, pk3}, message, popSigAgg);
G1Element popAggPk = pk1 + pk2 + pk3;
ok = PopSchemeMPL().Verify(popAggPk, message, popSigAgg);
PrivateKey aggSk = PrivateKey::Aggregate({sk1, sk2, sk3});
ok = (PopSchemeMPL().Sign(aggSk, message) == popSigAgg);
PrivateKey masterSk = AugSchemeMPL().KeyGen(seed);
PrivateKey child = AugSchemeMPL().DeriveChildSk(masterSk, 152);
PrivateKey grandChild = AugSchemeMPL().DeriveChildSk(child, 952)
G1Element masterPk = masterSk.GetG1Element();
PrivateKey childU = AugSchemeMPL().DeriveChildSkUnhardened(masterSk, 22);
PrivateKey grandchildU = AugSchemeMPL().DeriveChildSkUnhardened(childU, 0);
G1Element childUPk = AugSchemeMPL().DeriveChildPkUnhardened(masterPk, 22);
G1Element grandchildUPk = AugSchemeMPL().DeriveChildPkUnhardened(childUPk, 0);
ok = (grandchildUPk == grandchildU.GetG1Element();
Build
Cmake 3.14+, a c++ compiler, python3 and python[3.x]-dev (for bindings) are required for building.
mkdir build
cd build
cmake ../
cmake --build . -- -j 6
Run tests
./build/src/runtest
Run benchmarks
./build/src/runbench
On a 3.5 GHz i7 Mac, verification takes about 1.1ms per signature, and signing takes 1.3ms.
Link the library to use it
g++ -Wl,-no_pie -std=c++11 -Ibls-signatures/src -L./bls-signatures/build/ -l bls yourapp.cpp
Notes on dependencies
We use Libsodium which provides secure memory
allocation. To install it, either download them from github and
follow the instructions for each repo, or use a package manager like APT or
brew. You can follow the recipe used to build python wheels for multiple
platforms in .github/workflows/
.
Discussion
Discussion about this library and other Chia related development is in the #chia-development
channel of Chia's Discord.
Code style
- Always use vector<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
- Use SecAlloc and SecFree when handling secrets
ci Building
The primary build process for this repository is to use GitHub Actions to
build binary wheels for MacOS, Linux (x64 and aarch64), and Windows and publish
them with a source wheel on PyPi. MacOS ARM64 is also supported.
See .github/workflows/build.yml
. CMake uses
FetchContent
to download pybind11 for the Python
bindings. Building
is then managed by cibuildwheel.
Further installation is then available via pip install blspy
e.g. The ci
builds include a statically linked libsodium.
Contributing and workflow
Contributions are welcome and more details are available in chia-blockchain's
CONTRIBUTING.md.
The main branch is usually the currently released latest version on PyPI.
Note that at times bls-signatures/blspy will be ahead of the release version
that chia-blockchain requires in it's main/release version in preparation
for a new chia-blockchain release. Please branch or fork main and then create
a pull request to the main branch. Linear merging is enforced on main and
merging requires a completed review. PRs will kick off a GitHub actions ci
for building and testing.
Specification and test vectors
The IETF bls draft
is followed. Test vectors can also be seen in the python and cpp test files.
Libsodium license
The libsodium static library is licensed under the ISC license which requires
the following copyright notice.
ISC License
Copyright (c) 2013-2020
Frank Denis <j at pureftpd dot org>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
BLST license
BLST is used with the
Apache 2.0 license