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

Commit e573ef3

Browse files
authored
Merge pull request #708 from mhchia/feature/add-bls-bindings
Add bindings for BLS from Chia network
2 parents 52d73e3 + 4f8acee commit e573ef3

7 files changed

Lines changed: 318 additions & 0 deletions

File tree

.circleci/config.yml

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,48 @@ p2pd_steps: &p2pd_steps
124124
- ~/.local
125125
key: cache-v1-{{ arch }}-{{ .Environment.CIRCLE_JOB }}-{{ checksum "setup.py" }}-{{ checksum "tox.ini" }}
126126

127+
cmake: &cmake
128+
working_directory: ~/repo
129+
steps:
130+
- checkout
131+
- run:
132+
name: checkout fixtures submodule
133+
command: git submodule update --init --recursive
134+
- run:
135+
name: merge pull request base
136+
command: ./.circleci/merge_pr.sh
137+
- run:
138+
name: merge pull request base (2nd try)
139+
command: ./.circleci/merge_pr.sh
140+
when: on_fail
141+
- run:
142+
name: merge pull request base (3nd try)
143+
command: ./.circleci/merge_pr.sh
144+
when: on_fail
145+
- restore_cache:
146+
keys:
147+
- cache-v1-{{ arch }}-{{ .Environment.CIRCLE_JOB }}-{{ checksum "setup.py" }}-{{ checksum "tox.ini" }}
148+
- run:
149+
name: install libsnappy-dev
150+
command: sudo apt install -y libsnappy-dev
151+
- run:
152+
name: install cmake
153+
command: sudo apt-get update && sudo apt-get install -y gcc g++ cmake
154+
- run:
155+
name: install dependencies
156+
command: pip install --user tox
157+
- run:
158+
name: run tox
159+
command: ~/.local/bin/tox
160+
- save_cache:
161+
paths:
162+
- .hypothesis
163+
- .tox
164+
- ~/.cache/pip
165+
- ~/.local
166+
- ./eggs
167+
key: cache-v1-{{ arch }}-{{ .Environment.CIRCLE_JOB }}-{{ checksum "setup.py" }}-{{ checksum "tox.ini" }}
168+
127169
jobs:
128170
py36-lint:
129171
<<: *common
@@ -250,6 +292,12 @@ jobs:
250292
environment:
251293
TOXENV: py36-libp2p
252294
<<: *p2pd_steps
295+
py36-bls-bindings:
296+
<<: *cmake
297+
docker:
298+
- image: circleci/python:3.6
299+
environment:
300+
TOXENV: py36-bls-bindings
253301
py36-wheel-cli:
254302
<<: *common
255303
docker:
@@ -324,6 +372,12 @@ jobs:
324372
environment:
325373
TOXENV: py37-libp2p
326374
<<: *p2pd_steps
375+
py37-bls-bindings:
376+
<<: *cmake
377+
docker:
378+
- image: circleci/python:3.7
379+
environment:
380+
TOXENV: py37-bls-bindings
327381
py37-wheel-cli:
328382
<<: *common
329383
docker:
@@ -357,6 +411,7 @@ workflows:
357411
- py37-eth2-fixtures
358412
- py37-eth2-integration
359413
# - py37-libp2p
414+
- py37-bls-bindings
360415
- py37-plugins
361416

362417
- py37-rpc-state-quadratic
@@ -379,6 +434,7 @@ workflows:
379434
- py36-eth2-fixtures
380435
- py36-eth2-integration
381436
# - py36-libp2p
437+
- py36-bls-bindings
382438
- py36-plugins
383439

384440
- py36-integration

eth2/_utils/bls_bindings/__init__.py

Whitespace-only changes.

eth2/_utils/bls_bindings/chia_network/__init__.py

Whitespace-only changes.
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
from typing import (
2+
Sequence,
3+
cast,
4+
)
5+
6+
import blspy as bls_chia
7+
8+
from eth_typing import (
9+
BLSPubkey,
10+
BLSSignature,
11+
Hash32,
12+
)
13+
from eth_utils import (
14+
ValidationError,
15+
)
16+
17+
18+
def _privkey_int_to_bytes(privkey: int) -> bytes:
19+
return privkey.to_bytes(bls_chia.PrivateKey.PRIVATE_KEY_SIZE, "big")
20+
21+
22+
def combine_domain(message_hash: Hash32, domain: int) -> bytes:
23+
return message_hash + domain.to_bytes(8, 'big')
24+
25+
26+
def sign(message_hash: Hash32,
27+
privkey: int,
28+
domain: int) -> BLSSignature:
29+
privkey_chia = bls_chia.PrivateKey.from_bytes(_privkey_int_to_bytes(privkey))
30+
sig_chia = privkey_chia.sign_insecure(
31+
combine_domain(message_hash, domain)
32+
)
33+
sig_chia_bytes = sig_chia.serialize()
34+
return cast(BLSSignature, sig_chia_bytes)
35+
36+
37+
def privtopub(k: int) -> BLSPubkey:
38+
privkey_chia = bls_chia.PrivateKey.from_bytes(_privkey_int_to_bytes(k))
39+
return cast(BLSPubkey, privkey_chia.get_public_key().serialize())
40+
41+
42+
def verify(message_hash: Hash32, pubkey: BLSPubkey, signature: BLSSignature, domain: int) -> bool:
43+
pubkey_chia = bls_chia.PublicKey.from_bytes(pubkey)
44+
signature_chia = bls_chia.Signature.from_bytes(signature)
45+
signature_chia.set_aggregation_info(
46+
bls_chia.AggregationInfo.from_msg(
47+
pubkey_chia,
48+
combine_domain(message_hash, domain),
49+
)
50+
)
51+
return cast(bool, signature_chia.verify())
52+
53+
54+
def aggregate_signatures(signatures: Sequence[BLSSignature]) -> BLSSignature:
55+
signatures_chia = [
56+
bls_chia.InsecureSignature.from_bytes(signature)
57+
for signature in signatures
58+
]
59+
aggregated_signature = bls_chia.InsecureSignature.aggregate(signatures_chia)
60+
aggregated_signature_bytes = aggregated_signature.serialize()
61+
return cast(BLSSignature, aggregated_signature_bytes)
62+
63+
64+
def aggregate_pubkeys(pubkeys: Sequence[BLSPubkey]) -> BLSPubkey:
65+
pubkeys_chia = [
66+
bls_chia.PublicKey.from_bytes(pubkey)
67+
for pubkey in pubkeys
68+
]
69+
aggregated_pubkey_chia = bls_chia.PublicKey.aggregate_insecure(pubkeys_chia)
70+
return cast(BLSPubkey, aggregated_pubkey_chia.serialize())
71+
72+
73+
def verify_multiple(pubkeys: Sequence[BLSPubkey],
74+
message_hashes: Sequence[Hash32],
75+
signature: BLSSignature,
76+
domain: int) -> bool:
77+
78+
len_msgs = len(message_hashes)
79+
80+
if len(pubkeys) != len_msgs:
81+
raise ValidationError(
82+
"len(pubkeys) (%s) should be equal to len(message_hashes) (%s)" % (
83+
len(pubkeys), len_msgs
84+
)
85+
)
86+
87+
message_hashes_with_domain = [
88+
combine_domain(message_hash, domain)
89+
for message_hash in message_hashes
90+
]
91+
pubkeys_chia = map(bls_chia.PublicKey.from_bytes, pubkeys)
92+
aggregate_infos = [
93+
bls_chia.AggregationInfo.from_msg(pubkey_chia, message_hash)
94+
for pubkey_chia, message_hash in zip(pubkeys_chia, message_hashes_with_domain)
95+
]
96+
merged_info = bls_chia.AggregationInfo.merge_infos(aggregate_infos)
97+
98+
signature_chia = bls_chia.Signature.from_bytes(signature)
99+
signature_chia.set_aggregation_info(merged_info)
100+
return cast(bool, signature_chia.verify())

setup.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,9 @@
101101
"protobuf>=3.6.1",
102102
"pymultihash>=0.8.2",
103103
],
104+
'bls-bindings': [
105+
"blspy>=0.1.8,<1", # for `bls_chia`
106+
],
104107
}
105108

106109
# NOTE: Snappy breaks RTD builds. Until we have a more mature solution
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
import pytest
2+
3+
from py_ecc.optimized_bls12_381 import (
4+
curve_order,
5+
)
6+
7+
from eth2._utils.bls_bindings.chia_network.api import (
8+
aggregate_pubkeys,
9+
aggregate_signatures,
10+
privtopub,
11+
sign,
12+
verify,
13+
verify_multiple,
14+
)
15+
16+
17+
def assert_pubkey(obj):
18+
assert isinstance(obj, bytes) and len(obj) == 48
19+
20+
21+
def assert_signature(obj):
22+
assert isinstance(obj, bytes) and len(obj) == 96
23+
24+
25+
def test_sanity():
26+
msg_0 = b"\x32" * 32
27+
domain = 123
28+
29+
# Test: Verify the basic sign/verify process
30+
privkey_0 = 5566
31+
sig_0 = sign(msg_0, privkey_0, domain)
32+
assert_signature(sig_0)
33+
pubkey_0 = privtopub(privkey_0)
34+
assert_pubkey(pubkey_0)
35+
assert verify(msg_0, pubkey_0, sig_0, domain)
36+
37+
privkey_1 = 5567
38+
sig_1 = sign(msg_0, privkey_1, domain)
39+
pubkey_1 = privtopub(privkey_1)
40+
assert verify(msg_0, pubkey_1, sig_1, domain)
41+
42+
# Test: Verify signatures are correctly aggregated
43+
aggregated_signature = aggregate_signatures([sig_0, sig_1])
44+
assert_signature(aggregated_signature)
45+
46+
# Test: Verify pubkeys are correctly aggregated
47+
aggregated_pubkey = aggregate_pubkeys([pubkey_0, pubkey_1])
48+
assert_pubkey(aggregated_pubkey)
49+
50+
# Test: Verify with `aggregated_signature` and `aggregated_pubkey`
51+
assert verify(msg_0, aggregated_pubkey, aggregated_signature, domain)
52+
53+
# Test: `verify_multiple`
54+
msg_1 = b"x22" * 32
55+
privkey_2 = 55688
56+
sig_2 = sign(msg_1, privkey_2, domain)
57+
assert_signature(sig_2)
58+
pubkey_2 = privtopub(privkey_2)
59+
assert_pubkey(pubkey_2)
60+
sig_1_2 = aggregate_signatures([sig_1, sig_2])
61+
assert verify_multiple(
62+
pubkeys=[pubkey_1, pubkey_2],
63+
message_hashes=[msg_0, msg_1],
64+
signature=sig_1_2,
65+
domain=domain,
66+
)
67+
68+
69+
@pytest.mark.parametrize(
70+
'privkey',
71+
[
72+
(1),
73+
(5),
74+
(124),
75+
(735),
76+
(127409812145),
77+
(90768492698215092512159),
78+
(curve_order - 1),
79+
]
80+
)
81+
def test_bls_core(privkey):
82+
domain = 0
83+
msg = str(privkey).encode('utf-8')
84+
sig = sign(msg, privkey, domain=domain)
85+
pub = privtopub(privkey)
86+
assert verify(msg, pub, sig, domain=domain)
87+
88+
89+
@pytest.mark.parametrize(
90+
'msg, privkeys',
91+
[
92+
(b'\x12' * 32, [1, 5, 124, 735, 127409812145, 90768492698215092512159, curve_order - 1]),
93+
(b'\x34' * 32, [42, 666, 1274099945, 4389392949595]),
94+
]
95+
)
96+
def test_signature_aggregation(msg, privkeys):
97+
domain = 0
98+
sigs = [sign(msg, k, domain=domain) for k in privkeys]
99+
pubs = [privtopub(k) for k in privkeys]
100+
aggsig = aggregate_signatures(sigs)
101+
aggpub = aggregate_pubkeys(pubs)
102+
assert verify(msg, aggpub, aggsig, domain=domain)
103+
104+
105+
@pytest.mark.parametrize(
106+
'msg_1, msg_2',
107+
[
108+
(b'\x12' * 32, b'\x34' * 32)
109+
]
110+
)
111+
@pytest.mark.parametrize(
112+
'privkeys_1, privkeys_2',
113+
[
114+
(tuple(range(1, 11)), tuple(range(1, 11))),
115+
((1, 2, 3), (4, 5, 6, 7)),
116+
((1, 2, 3), (2, 3, 4, 5)),
117+
]
118+
)
119+
def test_multi_aggregation(msg_1, msg_2, privkeys_1, privkeys_2):
120+
domain = 0
121+
122+
sigs_1 = [sign(msg_1, k, domain=domain) for k in privkeys_1] # signatures to msg_1
123+
pubs_1 = [privtopub(k) for k in privkeys_1]
124+
aggsig_1 = aggregate_signatures(sigs_1)
125+
aggpub_1 = aggregate_pubkeys(pubs_1) # sig_1 to msg_1
126+
127+
sigs_2 = [sign(msg_2, k, domain=domain) for k in privkeys_2] # signatures to msg_2
128+
pubs_2 = [privtopub(k) for k in privkeys_2]
129+
aggsig_2 = aggregate_signatures(sigs_2)
130+
aggpub_2 = aggregate_pubkeys(pubs_2) # sig_2 to msg_2
131+
132+
message_hashes = [msg_1, msg_2]
133+
pubs = [aggpub_1, aggpub_2]
134+
aggsig = aggregate_signatures([aggsig_1, aggsig_2])
135+
136+
assert verify_multiple(
137+
pubkeys=pubs,
138+
message_hashes=message_hashes,
139+
signature=aggsig,
140+
domain=domain,
141+
)

tox.ini

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ envlist=
55
py36-rpc-blockchain
66
py36-rpc-state-{frontier,homestead,tangerine_whistle,spurious_dragon,byzantium,constantinople,petersburg}
77
py37-rpc-state-{quadratic,sstore,zero_knowledge}
8+
py{36,37}-bls-bindings
89
py{36,37}-libp2p
910
py{36,37}-lint
1011
py{36,37}-wheel-cli
@@ -131,6 +132,23 @@ deps = {[libp2p]deps}
131132
passenv = {[libp2p]passenv}
132133
commands = {[libp2p]commands}
133134

135+
[bls-bindings]
136+
deps = .[bls-bindings,test]
137+
passenv =
138+
TRAVIS_EVENT_TYPE
139+
commands =
140+
pytest -n 4 {posargs:tests/eth2/bls-bindings}
141+
142+
[testenv:py36-bls-bindings]
143+
deps = {[bls-bindings]deps}
144+
passenv = {[bls-bindings]passenv}
145+
commands = {[bls-bindings]commands}
146+
147+
[testenv:py37-bls-bindings]
148+
deps = {[bls-bindings]deps}
149+
passenv = {[bls-bindings]passenv}
150+
commands = {[bls-bindings]commands}
151+
134152
[common-lint]
135153
deps = .[p2p,trinity,lint,eth2,libp2p]
136154
commands=

0 commit comments

Comments
 (0)