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

Quick way to apply Chia BLS #764

Closed
wants to merge 25 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
3e2be61
enshrine chia backend
ChihChengLiang Jul 3, 2019
05cc60d
fix chia bls invalid private key range
ChihChengLiang Jul 3, 2019
b65fba3
install blspy
ChihChengLiang Jul 3, 2019
b62b92c
tuning inputs
ChihChengLiang Jul 3, 2019
257cd89
add cmake in circle
ChihChengLiang Jul 3, 2019
079ef87
fix mock py_ecc
ChihChengLiang Jul 3, 2019
d3e5be0
separate lightchain integration
ChihChengLiang Jul 3, 2019
6160ef3
fix docker
ChihChengLiang Jul 3, 2019
325f18c
fix private key range
ChihChengLiang Jul 3, 2019
59d1624
remove eth2 from install requires
ChihChengLiang Jul 3, 2019
b7df842
fix tox
ChihChengLiang Jul 3, 2019
75798e3
don't messup everything else, just add cmake everywhere
ChihChengLiang Jul 3, 2019
89d96fc
fix empty sig and pubkey
ChihChengLiang Jul 3, 2019
a52b6b0
make typing happy
ChihChengLiang Jul 4, 2019
85cfee5
skip to be updated test
ChihChengLiang Jul 4, 2019
e43d1ba
chia bls likes all 00 empty sig
ChihChengLiang Jul 4, 2019
7bbe855
Fix public key error message
ChihChengLiang Jul 4, 2019
df7f6cd
handle privkey error nicer
ChihChengLiang Jul 4, 2019
4328095
handle value error too
ChihChengLiang Jul 4, 2019
74d8fb0
more check, typing, and fixes
ChihChengLiang Jul 4, 2019
296640f
Update eth2/_utils/bls_bindings/chia_network/api.py
ChihChengLiang Jul 5, 2019
6692066
Update eth2/_utils/bls_bindings/chia_network/api.py
ChihChengLiang Jul 5, 2019
c6ba90e
PR feedback
ChihChengLiang Jul 5, 2019
34a9c19
add empty public key test
ChihChengLiang Jul 7, 2019
aa640b0
raise ValidationError when bls verify False
ChihChengLiang Jul 7, 2019
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
52 changes: 8 additions & 44 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ common: &common
- run:
name: install libsnappy-dev
command: sudo apt install -y libsnappy-dev
- run:
name: install cmake
command: sudo apt install -y gcc g++ cmake
- run:
name: install dependencies
command: pip install --user tox
Expand All @@ -52,6 +55,9 @@ geth_steps: &geth_steps
- run:
name: install libsnappy-dev
command: sudo apt install -y libsnappy-dev
- run:
name: install cmake
command: sudo apt install -y gcc g++ cmake
- run:
name: install dependencies
command: pip install --user tox
Expand Down Expand Up @@ -124,48 +130,6 @@ 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 @@ -293,7 +257,7 @@ jobs:
TOXENV: py36-libp2p
<<: *p2pd_steps
py36-bls-bindings:
<<: *cmake
<<: *common
docker:
- image: circleci/python:3.6
environment:
Expand Down Expand Up @@ -373,7 +337,7 @@ jobs:
TOXENV: py37-libp2p
<<: *p2pd_steps
py37-bls-bindings:
<<: *cmake
<<: *common
docker:
- image: circleci/python:3.7
environment:
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ COPY . /usr/src/app

# Install deps
RUN apt-get update
RUN apt-get -y install libsnappy-dev
RUN apt-get -y install libsnappy-dev gcc g++ cmake

RUN pip install -e .[dev] --no-cache-dir
RUN pip install -U trinity --no-cache-dir
Expand Down
127 changes: 127 additions & 0 deletions eth2/_utils/bls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@

from .bls_bindings.chia_network import (
api as chia_api,
)
from abc import (
ABC,
abstractmethod,
)
from typing import (
Sequence,
)
from eth_typing import (
BLSPubkey,
BLSSignature,
Hash32,
)
from eth_utils import (
ValidationError,
)


class BaseBLSBackend(ABC):

@staticmethod
@abstractmethod
def privtopub(k: int) -> BLSPubkey:
pass

@staticmethod
@abstractmethod
def sign(message_hash: Hash32,
privkey: int,
domain: int) -> BLSSignature:
pass

@staticmethod
@abstractmethod
def _verify(message_hash: Hash32,
pubkey: BLSPubkey,
signature: BLSSignature,
domain: int) -> bool:
pass

@classmethod
def verify(cls,
message_hash: Hash32,
pubkey: BLSPubkey,
signature: BLSSignature,
domain: int) -> None:
if not cls._verify(message_hash, pubkey, signature, domain):
raise ValidationError((
"Verification failed:\n"
f"message_hash {message_hash}\n"
f"pubkey {pubkey}\n"
f"signature {signature}\n"
f"domain {domain}"
))

@staticmethod
@abstractmethod
def aggregate_signatures(signatures: Sequence[BLSSignature]) -> BLSSignature:
pass

@staticmethod
@abstractmethod
def aggregate_pubkeys(pubkeys: Sequence[BLSPubkey]) -> BLSPubkey:
pass

@staticmethod
@abstractmethod
def _verify_multiple(pubkeys: Sequence[BLSPubkey],
message_hashes: Sequence[Hash32],
signature: BLSSignature,
domain: int) -> bool:
pass

@classmethod
def verify_multiple(cls,
pubkeys: Sequence[BLSPubkey],
message_hashes: Sequence[Hash32],
signature: BLSSignature,
domain: int) -> None:
if not cls._verify_multiple(pubkeys, message_hashes, signature, domain):
raise ValidationError((
"Verification failed:\n"
f"pubkeys {pubkeys}\n"
f"message_hashes {message_hashes}\n"
f"signature {signature}\n"
f"domain {domain}"
))


class ChiaBackend(BaseBLSBackend):
@staticmethod
def privtopub(k: int) -> BLSPubkey:
return chia_api.privtopub(k)

@staticmethod
def sign(message_hash: Hash32,
privkey: int,
domain: int) -> BLSSignature:
return chia_api.sign(message_hash, privkey, domain)

@staticmethod
def _verify(message_hash: Hash32,
pubkey: BLSPubkey,
signature: BLSSignature,
domain: int) -> bool:
return chia_api.verify(message_hash, pubkey, signature, domain)

@staticmethod
def aggregate_signatures(signatures: Sequence[BLSSignature]) -> BLSSignature:
return chia_api.aggregate_signatures(signatures)

@staticmethod
def aggregate_pubkeys(pubkeys: Sequence[BLSPubkey]) -> BLSPubkey:
return chia_api.aggregate_pubkeys(pubkeys)

@staticmethod
def _verify_multiple(pubkeys: Sequence[BLSPubkey],
message_hashes: Sequence[Hash32],
signature: BLSSignature,
domain: int) -> bool:
return chia_api.verify_multiple(pubkeys, message_hashes, signature, domain)


eth2_bls = ChiaBackend()
66 changes: 54 additions & 12 deletions eth2/_utils/bls_bindings/chia_network/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,46 @@
ValidationError,
)

from py_ecc.optimized_bls12_381 import (
curve_order,
)

from eth2.beacon.constants import (
EMPTY_PUBKEY,
EMPTY_SIGNATURE,
)


def _privkey_from_int(privkey: int) -> bls_chia.PrivateKey:
if privkey <= 0 or privkey >= curve_order:
raise ValidationError(
f"Invalid private key: Expect integer between 0 and {curve_order}, got {privkey}"
)
privkey_bytes = privkey.to_bytes(bls_chia.PrivateKey.PRIVATE_KEY_SIZE, "big")
try:
return bls_chia.PrivateKey.from_bytes(privkey_bytes)
except RuntimeError as error:
raise ValidationError(f"Bad private key: {privkey}, {error}")

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

def _pubkey_from_bytes(pubkey: BLSPubkey) -> bls_chia.PublicKey:
try:
return bls_chia.PublicKey.from_bytes(pubkey)
except (RuntimeError, ValueError) as error:
raise ValidationError(f"Bad public key: {pubkey}, {error}")


def _signature_from_bytes(signature: BLSSignature) -> bls_chia.Signature:
if signature == EMPTY_SIGNATURE:
raise ValidationError(f"Invalid signature (EMPTY_SIGNATURE): {signature}")
elif len(signature) != bls_chia.Signature.SIGNATURE_SIZE:
raise ValidationError(
f"Invalid signaute length, expect 96 got {len(signature)}. Signature: {signature}"
)
try:
return bls_chia.Signature.from_bytes(signature)
except (RuntimeError, ValueError) as error:
raise ValidationError(f"Bad signature: {signature}, {error}")


def combine_domain(message_hash: Hash32, domain: int) -> bytes:
Expand All @@ -26,7 +63,7 @@ def combine_domain(message_hash: Hash32, domain: int) -> bytes:
def sign(message_hash: Hash32,
privkey: int,
domain: int) -> BLSSignature:
privkey_chia = bls_chia.PrivateKey.from_bytes(_privkey_int_to_bytes(privkey))
privkey_chia = _privkey_from_int(privkey)
sig_chia = privkey_chia.sign_insecure(
combine_domain(message_hash, domain)
)
Expand All @@ -35,13 +72,13 @@ def sign(message_hash: Hash32,


def privtopub(k: int) -> BLSPubkey:
privkey_chia = bls_chia.PrivateKey.from_bytes(_privkey_int_to_bytes(k))
privkey_chia = _privkey_from_int(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)
pubkey_chia = _pubkey_from_bytes(pubkey)
signature_chia = _signature_from_bytes(signature)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not sure if we want the verify to return False or raise ValidationError for the invalid inputs.

Copy link
Contributor

Choose a reason for hiding this comment

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

Personally, I prefer ValidationError, since the caller cannot distinguish whether it is invalid inputs or just the incorrect signatures/wrong private keys if verify returns False.

signature_chia.set_aggregation_info(
bls_chia.AggregationInfo.from_msg(
pubkey_chia,
Expand All @@ -52,6 +89,9 @@ def verify(message_hash: Hash32, pubkey: BLSPubkey, signature: BLSSignature, dom


def aggregate_signatures(signatures: Sequence[BLSSignature]) -> BLSSignature:
if len(signatures) == 0:
return EMPTY_SIGNATURE
ChihChengLiang marked this conversation as resolved.
Show resolved Hide resolved

signatures_chia = [
bls_chia.InsecureSignature.from_bytes(signature)
for signature in signatures
Expand All @@ -62,8 +102,10 @@ def aggregate_signatures(signatures: Sequence[BLSSignature]) -> BLSSignature:


def aggregate_pubkeys(pubkeys: Sequence[BLSPubkey]) -> BLSPubkey:
if len(pubkeys) == 0:
return EMPTY_PUBKEY
pubkeys_chia = [
bls_chia.PublicKey.from_bytes(pubkey)
_pubkey_from_bytes(pubkey)
for pubkey in pubkeys
]
aggregated_pubkey_chia = bls_chia.PublicKey.aggregate_insecure(pubkeys_chia)
Expand All @@ -74,27 +116,27 @@ def verify_multiple(pubkeys: Sequence[BLSPubkey],
message_hashes: Sequence[Hash32],
signature: BLSSignature,
domain: int) -> bool:

len_msgs = len(message_hashes)
len_pubkeys = len(pubkeys)

if len(pubkeys) != len_msgs:
if len_pubkeys != len_msgs:
raise ValidationError(
"len(pubkeys) (%s) should be equal to len(message_hashes) (%s)" % (
len(pubkeys), len_msgs
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)
pubkeys_chia = map(_pubkey_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 = _signature_from_bytes(signature)
signature_chia.set_aggregation_info(merged_info)
return cast(bool, signature_chia.verify())
2 changes: 2 additions & 0 deletions eth2/beacon/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
)
from eth_typing import (
BLSSignature,
BLSPubkey,
)
from eth2.beacon.typing import (
Epoch,
Expand All @@ -11,6 +12,7 @@


EMPTY_SIGNATURE = BLSSignature(b'\x00' * 96)
EMPTY_PUBKEY = BLSPubkey(b'\x00' * 48)
GWEI_PER_ETH = 10**9
FAR_FUTURE_EPOCH = Epoch(2**64 - 1)

Expand Down
2 changes: 1 addition & 1 deletion eth2/beacon/deposit_helpers.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from eth_utils import (
ValidationError,
)
from py_ecc import bls
from eth2._utils.bls import eth2_bls as bls
import ssz

from eth2._utils.hash import (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
ZERO_HASH32,
)

from py_ecc import bls
from eth2._utils.bls import eth2_bls as bls
from eth2._utils import (
bitfield,
)
Expand Down
2 changes: 1 addition & 1 deletion eth2/beacon/tools/builder/validator.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
from eth.constants import (
ZERO_HASH32,
)
from py_ecc import bls
from eth2._utils.bls import eth2_bls as bls

from eth2._utils.bitfield import (
get_empty_bitfield,
Expand Down
Loading