Skip to content
This repository has been archived by the owner on Jul 1, 2021. It is now read-only.

Add bindings for BLS from Chia network #708

Merged
merged 5 commits into from
Jun 24, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,48 @@ p2pd_steps: &p2pd_steps
- ~/.local
key: cache-v1-{{ arch }}-{{ .Environment.CIRCLE_JOB }}-{{ checksum "setup.py" }}-{{ checksum "tox.ini" }}

cmake: &cmake
working_directory: ~/repo
steps:
- checkout
- run:
name: checkout fixtures submodule
command: git submodule update --init --recursive
- run:
name: merge pull request base
command: ./.circleci/merge_pr.sh
- run:
name: merge pull request base (2nd try)
command: ./.circleci/merge_pr.sh
when: on_fail
- run:
name: merge pull request base (3nd try)
command: ./.circleci/merge_pr.sh
when: on_fail
- restore_cache:
keys:
- cache-v1-{{ arch }}-{{ .Environment.CIRCLE_JOB }}-{{ checksum "setup.py" }}-{{ checksum "tox.ini" }}
- run:
name: install libsnappy-dev
command: sudo apt install -y libsnappy-dev
- run:
name: install cmake
command: sudo apt-get update && sudo apt-get install -y gcc g++ cmake
- run:
name: install dependencies
command: pip install --user tox
- run:
name: run tox
command: ~/.local/bin/tox
- save_cache:
paths:
- .hypothesis
- .tox
- ~/.cache/pip
- ~/.local
- ./eggs
key: cache-v1-{{ arch }}-{{ .Environment.CIRCLE_JOB }}-{{ checksum "setup.py" }}-{{ checksum "tox.ini" }}

jobs:
py36-lint:
<<: *common
Expand Down Expand Up @@ -250,6 +292,12 @@ jobs:
environment:
TOXENV: py36-libp2p
<<: *p2pd_steps
py36-bls-bindings:
<<: *cmake
docker:
- image: circleci/python:3.6
environment:
TOXENV: py36-bls-bindings
py36-wheel-cli:
<<: *common
docker:
Expand Down Expand Up @@ -324,6 +372,12 @@ jobs:
environment:
TOXENV: py37-libp2p
<<: *p2pd_steps
py37-bls-bindings:
<<: *cmake
docker:
- image: circleci/python:3.7
environment:
TOXENV: py37-bls-bindings
py37-wheel-cli:
<<: *common
docker:
Expand Down Expand Up @@ -357,6 +411,7 @@ workflows:
- py37-eth2-fixtures
- py37-eth2-integration
# - py37-libp2p
- py37-bls-bindings
- py37-plugins

- py37-rpc-state-quadratic
Expand All @@ -379,6 +434,7 @@ workflows:
- py36-eth2-fixtures
- py36-eth2-integration
# - py36-libp2p
- py36-bls-bindings
- py36-plugins

- py36-integration
Expand Down
Empty file.
Empty file.
100 changes: 100 additions & 0 deletions eth2/_utils/bls_bindings/chia_network/api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
from typing import (
Sequence,
cast,
)

import blspy as bls_chia

from eth_typing import (
BLSPubkey,
BLSSignature,
Hash32,
)
from eth_utils import (
ValidationError,
)


def _privkey_int_to_bytes(privkey: int) -> bytes:
return privkey.to_bytes(bls_chia.PrivateKey.PRIVATE_KEY_SIZE, "big")


def combine_domain(message_hash: Hash32, domain: int) -> bytes:
return message_hash + domain.to_bytes(8, 'big')


def sign(message_hash: Hash32,
privkey: int,
domain: int) -> BLSSignature:
privkey_chia = bls_chia.PrivateKey.from_bytes(_privkey_int_to_bytes(privkey))
sig_chia = privkey_chia.sign_insecure(
combine_domain(message_hash, domain)
)
sig_chia_bytes = sig_chia.serialize()
return cast(BLSSignature, sig_chia_bytes)


def privtopub(k: int) -> BLSPubkey:
privkey_chia = bls_chia.PrivateKey.from_bytes(_privkey_int_to_bytes(k))
return cast(BLSPubkey, privkey_chia.get_public_key().serialize())


def verify(message_hash: Hash32, pubkey: BLSPubkey, signature: BLSSignature, domain: int) -> bool:
pubkey_chia = bls_chia.PublicKey.from_bytes(pubkey)
signature_chia = bls_chia.Signature.from_bytes(signature)
signature_chia.set_aggregation_info(
bls_chia.AggregationInfo.from_msg(
pubkey_chia,
combine_domain(message_hash, domain),
)
)
return cast(bool, signature_chia.verify())


def aggregate_signatures(signatures: Sequence[BLSSignature]) -> BLSSignature:
signatures_chia = [
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do tuple here? Looks like the test is happy with tuple.

bls_chia.InsecureSignature.from_bytes(signature)
for signature in signatures
]
aggregated_signature = bls_chia.InsecureSignature.aggregate(signatures_chia)
aggregated_signature_bytes = aggregated_signature.serialize()
return cast(BLSSignature, aggregated_signature_bytes)


def aggregate_pubkeys(pubkeys: Sequence[BLSPubkey]) -> BLSPubkey:
pubkeys_chia = [
bls_chia.PublicKey.from_bytes(pubkey)
for pubkey in pubkeys
]
aggregated_pubkey_chia = bls_chia.PublicKey.aggregate_insecure(pubkeys_chia)
return cast(BLSPubkey, aggregated_pubkey_chia.serialize())


def verify_multiple(pubkeys: Sequence[BLSPubkey],
message_hashes: Sequence[Hash32],
signature: BLSSignature,
domain: int) -> bool:

len_msgs = len(message_hashes)

if len(pubkeys) != len_msgs:
raise ValidationError(
"len(pubkeys) (%s) should be equal to len(message_hashes) (%s)" % (
len(pubkeys), len_msgs
)
)

message_hashes_with_domain = [
combine_domain(message_hash, domain)
for message_hash in message_hashes
]
pubkeys_chia = map(bls_chia.PublicKey.from_bytes, pubkeys)
aggregate_infos = [
bls_chia.AggregationInfo.from_msg(pubkey_chia, message_hash)
for pubkey_chia, message_hash in zip(pubkeys_chia, message_hashes_with_domain)
]
merged_info = bls_chia.AggregationInfo.merge_infos(aggregate_infos)

signature_chia = bls_chia.Signature.from_bytes(signature)
signature_chia.set_aggregation_info(merged_info)
return cast(bool, signature_chia.verify())
3 changes: 3 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,9 @@
"protobuf>=3.6.1",
"pymultihash>=0.8.2",
],
'bls-bindings': [
"blspy>=0.1.8,<1", # for `bls_chia`
],
}

# NOTE: Snappy breaks RTD builds. Until we have a more mature solution
Expand Down
141 changes: 141 additions & 0 deletions tests/eth2/bls-bindings/chia_network/test_api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import pytest

from py_ecc.optimized_bls12_381 import (
curve_order,
)

from eth2._utils.bls_bindings.chia_network.api import (
aggregate_pubkeys,
aggregate_signatures,
privtopub,
sign,
verify,
verify_multiple,
)


def assert_pubkey(obj):
assert isinstance(obj, bytes) and len(obj) == 48


def assert_signature(obj):
assert isinstance(obj, bytes) and len(obj) == 96


def test_sanity():
msg_0 = b"\x32" * 32
domain = 123

# Test: Verify the basic sign/verify process
privkey_0 = 5566
sig_0 = sign(msg_0, privkey_0, domain)
assert_signature(sig_0)
pubkey_0 = privtopub(privkey_0)
assert_pubkey(pubkey_0)
assert verify(msg_0, pubkey_0, sig_0, domain)

privkey_1 = 5567
sig_1 = sign(msg_0, privkey_1, domain)
pubkey_1 = privtopub(privkey_1)
assert verify(msg_0, pubkey_1, sig_1, domain)

# Test: Verify signatures are correctly aggregated
aggregated_signature = aggregate_signatures([sig_0, sig_1])
assert_signature(aggregated_signature)

# Test: Verify pubkeys are correctly aggregated
aggregated_pubkey = aggregate_pubkeys([pubkey_0, pubkey_1])
assert_pubkey(aggregated_pubkey)

# Test: Verify with `aggregated_signature` and `aggregated_pubkey`
assert verify(msg_0, aggregated_pubkey, aggregated_signature, domain)

# Test: `verify_multiple`
msg_1 = b"x22" * 32
privkey_2 = 55688
sig_2 = sign(msg_1, privkey_2, domain)
assert_signature(sig_2)
pubkey_2 = privtopub(privkey_2)
assert_pubkey(pubkey_2)
sig_1_2 = aggregate_signatures([sig_1, sig_2])
assert verify_multiple(
pubkeys=[pubkey_1, pubkey_2],
message_hashes=[msg_0, msg_1],
signature=sig_1_2,
domain=domain,
)


@pytest.mark.parametrize(
'privkey',
[
(1),
(5),
(124),
(735),
(127409812145),
(90768492698215092512159),
(curve_order - 1),
]
)
def test_bls_core(privkey):
domain = 0
msg = str(privkey).encode('utf-8')
sig = sign(msg, privkey, domain=domain)
pub = privtopub(privkey)
assert verify(msg, pub, sig, domain=domain)


@pytest.mark.parametrize(
'msg, privkeys',
[
(b'\x12' * 32, [1, 5, 124, 735, 127409812145, 90768492698215092512159, curve_order - 1]),
(b'\x34' * 32, [42, 666, 1274099945, 4389392949595]),
]
)
def test_signature_aggregation(msg, privkeys):
domain = 0
sigs = [sign(msg, k, domain=domain) for k in privkeys]
pubs = [privtopub(k) for k in privkeys]
aggsig = aggregate_signatures(sigs)
aggpub = aggregate_pubkeys(pubs)
assert verify(msg, aggpub, aggsig, domain=domain)


@pytest.mark.parametrize(
'msg_1, msg_2',
[
(b'\x12' * 32, b'\x34' * 32)
]
)
@pytest.mark.parametrize(
'privkeys_1, privkeys_2',
[
(tuple(range(1, 11)), tuple(range(1, 11))),
((1, 2, 3), (4, 5, 6, 7)),
((1, 2, 3), (2, 3, 4, 5)),
]
)
def test_multi_aggregation(msg_1, msg_2, privkeys_1, privkeys_2):
domain = 0

sigs_1 = [sign(msg_1, k, domain=domain) for k in privkeys_1] # signatures to msg_1
pubs_1 = [privtopub(k) for k in privkeys_1]
aggsig_1 = aggregate_signatures(sigs_1)
aggpub_1 = aggregate_pubkeys(pubs_1) # sig_1 to msg_1

sigs_2 = [sign(msg_2, k, domain=domain) for k in privkeys_2] # signatures to msg_2
pubs_2 = [privtopub(k) for k in privkeys_2]
aggsig_2 = aggregate_signatures(sigs_2)
aggpub_2 = aggregate_pubkeys(pubs_2) # sig_2 to msg_2

message_hashes = [msg_1, msg_2]
pubs = [aggpub_1, aggpub_2]
aggsig = aggregate_signatures([aggsig_1, aggsig_2])

assert verify_multiple(
pubkeys=pubs,
message_hashes=message_hashes,
signature=aggsig,
domain=domain,
)
18 changes: 18 additions & 0 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ envlist=
py36-rpc-blockchain
py36-rpc-state-{frontier,homestead,tangerine_whistle,spurious_dragon,byzantium,constantinople,petersburg}
py37-rpc-state-{quadratic,sstore,zero_knowledge}
py{36,37}-bls-bindings
py{36,37}-libp2p
py{36,37}-lint
py{36,37}-wheel-cli
Expand Down Expand Up @@ -131,6 +132,23 @@ deps = {[libp2p]deps}
passenv = {[libp2p]passenv}
commands = {[libp2p]commands}

[bls-bindings]
deps = .[bls-bindings,test]
passenv =
TRAVIS_EVENT_TYPE
commands =
pytest -n 4 {posargs:tests/eth2/bls-bindings}

[testenv:py36-bls-bindings]
deps = {[bls-bindings]deps}
passenv = {[bls-bindings]passenv}
commands = {[bls-bindings]commands}

[testenv:py37-bls-bindings]
deps = {[bls-bindings]deps}
passenv = {[bls-bindings]passenv}
commands = {[bls-bindings]commands}

[common-lint]
deps = .[p2p,trinity,lint,eth2,libp2p]
commands=
Expand Down