Skip to content

Update to IETF BLS draft-irtf-cfrg-bls-signature-02 + draft-irtf-cfrg-hash-to-curve-07 #1799

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 19 commits into from
May 18, 2020
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
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -501,7 +501,7 @@ def run(self):
"eth-utils>=1.3.0,<2",
"eth-typing>=2.1.0,<3.0.0",
"pycryptodome==3.9.4",
"py_ecc==2.0.0",
"py_ecc==4.0.0",
"dataclasses==0.6",
"remerkleable==0.1.13",
"ruamel.yaml==0.16.5",
Expand Down
6 changes: 4 additions & 2 deletions specs/phase0/beacon-chain.md
Original file line number Diff line number Diff line change
Expand Up @@ -603,16 +603,18 @@ def bytes_to_int(data: bytes) -> uint64:

#### BLS Signatures

Eth2 makes use of BLS signatures as specified in the [IETF draft BLS specification](https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-00). Specifically, eth2 uses the `BLS_SIG_BLS12381G2-SHA256-SSWU-RO-_POP_` ciphersuite which implements the following interfaces:
Eth2 makes use of BLS signatures as specified in the [IETF draft BLS specification draft-irtf-cfrg-bls-signature-02](https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-02) but uses [Hashing to Elliptic Curves - draft-irtf-cfrg-hash-to-curve-07](https://tools.ietf.org/html/draft-irtf-cfrg-hash-to-curve-07) instead of draft-irtf-cfrg-hash-to-curve-06. Specifically, eth2 uses the `BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_` ciphersuite which implements the following interfaces:

- `def Sign(SK: int, message: Bytes) -> BLSSignature`
- `def Verify(PK: BLSPubkey, message: Bytes, signature: BLSSignature) -> bool`
- `def Aggregate(signatures: Sequence[BLSSignature]) -> BLSSignature`
- `def FastAggregateVerify(PKs: Sequence[BLSPubkey], message: Bytes, signature: BLSSignature) -> bool`
- `def AggregateVerify(pairs: Sequence[PK: BLSPubkey, message: Bytes], signature: BLSSignature) -> bool`
- `def AggregateVerify(PKs: Sequence[BLSPubkey], messages: Sequence[Bytes], signature: BLSSignature) -> bool`

Within these specifications, BLS signatures are treated as a module for notational clarity, thus to verify a signature `bls.Verify(...)` is used.

*Note*: The non-standard configuration of the BLS and hash to curve specs is temporary and will be resolved once IETF releases BLS spec draft 3.

### Predicates

#### `is_active_validator`
Expand Down
2 changes: 1 addition & 1 deletion specs/phase0/validator.md
Original file line number Diff line number Diff line change
Expand Up @@ -446,7 +446,7 @@ def is_aggregator(state: BeaconState, slot: Slot, index: CommitteeIndex, slot_si

If the validator is selected to aggregate (`is_aggregator()`), they construct an aggregate attestation via the following.

Collect `attestations` seen via gossip during the `slot` that have an equivalent `attestation_data` to that constructed by the validator, and create an `aggregate_attestation: Attestation` with the following fields.
Collect `attestations` seen via gossip during the `slot` that have an equivalent `attestation_data` to that constructed by the validator. If `len(attestations) > 0`, create an `aggregate_attestation: Attestation` with the following fields.

##### Data

Expand Down
44 changes: 36 additions & 8 deletions specs/phase1/beacon-chain.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@
- [Updated `is_valid_indexed_attestation`](#updated-is_valid_indexed_attestation)
- [`is_shard_attestation`](#is_shard_attestation)
- [`is_winning_attestation`](#is_winning_attestation)
- [`optional_aggregate_verify`](#optional_aggregate_verify)
- [`optional_fast_aggregate_verify`](#optional_fast_aggregate_verify)
- [Block processing](#block-processing)
- [Operations](#operations)
- [New Attestation processing](#new-attestation-processing)
Expand Down Expand Up @@ -110,6 +112,7 @@ Configuration is not namespaced. Instead it is strictly an extension;
| `DOMAIN_SHARD_PROPOSAL` | `DomainType('0x80000000')` | |
| `DOMAIN_SHARD_COMMITTEE` | `DomainType('0x81000000')` | |
| `DOMAIN_LIGHT_CLIENT` | `DomainType('0x82000000')` | |
| `NO_SIGNATURE` | `BLSSignature(b'\x00' * 96)` | |

## Updated containers

Expand Down Expand Up @@ -596,7 +599,7 @@ def is_valid_indexed_attestation(state: BeaconState, indexed_attestation: Indexe
all_signing_roots.append(compute_signing_root(attestation_wrapper, domain))
else:
assert not cbit
return bls.AggregateVerify(zip(all_pubkeys, all_signing_roots), signature=attestation.signature)
return bls.AggregateVerify(all_pubkeys, all_signing_roots, signature=attestation.signature)
```

#### `is_shard_attestation`
Expand Down Expand Up @@ -633,6 +636,36 @@ def is_winning_attestation(state: BeaconState,
)
```

#### `optional_aggregate_verify`

```python
def optional_aggregate_verify(pubkeys: Sequence[BLSPubkey],
messages: Sequence[Bytes32],
signature: BLSSignature) -> bool:
"""
If ``pubkeys`` is an empty list, the given ``signature`` should be a stub ``NO_SIGNATURE``.
Otherwise, verify it with standard BLS AggregateVerify API.
"""
if len(pubkeys) == 0:
return signature == NO_SIGNATURE
else:
return bls.AggregateVerify(pubkeys, messages, signature)
```

#### `optional_fast_aggregate_verify`

```python
def optional_fast_aggregate_verify(pubkeys: Sequence[BLSPubkey], message: Bytes32, signature: BLSSignature) -> bool:
"""
If ``pubkeys`` is an empty list, the given ``signature`` should be a stub ``NO_SIGNATURE``.
Otherwise, verify it with standard BLS FastAggregateVerify API.
"""
if len(pubkeys) == 0:
return signature == NO_SIGNATURE
else:
return bls.FastAggregateVerify(pubkeys, message, signature)
```

### Block processing

```python
Expand Down Expand Up @@ -764,7 +797,7 @@ def apply_shard_transition(state: BeaconState, shard: Shard, transition: ShardTr
for header in headers
]
# Verify combined proposer signature
assert bls.AggregateVerify(zip(pubkeys, signing_roots), signature=transition.proposer_signature_aggregate)
assert optional_aggregate_verify(pubkeys, signing_roots, transition.proposer_signature_aggregate)

# Save updated state
state.shard_states[shard] = transition.shard_states[len(transition.shard_states) - 1]
Expand Down Expand Up @@ -942,12 +975,7 @@ def process_light_client_signatures(state: BeaconState, block_body: BeaconBlockB
slot = compute_previous_slot(state.slot)
signing_root = compute_signing_root(get_block_root_at_slot(state, slot),
get_domain(state, DOMAIN_LIGHT_CLIENT, compute_epoch_at_slot(slot)))
if len(signer_pubkeys) == 0:
# TODO: handle the empty light_client_signature case?
assert block_body.light_client_signature == BLSSignature()
return
else:
assert bls.FastAggregateVerify(signer_pubkeys, signing_root, signature=block_body.light_client_signature)
assert optional_fast_aggregate_verify(signer_pubkeys, signing_root, block_body.light_client_signature)
```

### Epoch transition
Expand Down
2 changes: 1 addition & 1 deletion specs/phase1/custody-game.md
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,7 @@ def process_early_derived_secret_reveal(state: BeaconState, reveal: EarlyDerived

domain = get_domain(state, DOMAIN_RANDAO, reveal.epoch)
signing_roots = [compute_signing_root(root, domain) for root in [hash_tree_root(reveal.epoch), reveal.mask]]
assert bls.AggregateVerify(zip(pubkeys, signing_roots), reveal.reveal)
assert bls.AggregateVerify(pubkeys, signing_roots, reveal.reveal)

if reveal.epoch >= get_current_epoch(state) + CUSTODY_PERIOD_TO_RANDAO_PADDING:
# Full slashing when the secret was revealed so early it may be a valid custody
Expand Down
7 changes: 5 additions & 2 deletions specs/phase1/shard-transition.md
Original file line number Diff line number Diff line change
Expand Up @@ -277,10 +277,13 @@ def get_shard_transition(beacon_state: BeaconState,
proposer_signatures = []
for proposal in proposals:
shard_block_lengths.append(len(proposal.message.body))
if proposal.signature != BLSSignature():
if proposal.signature != NO_SIGNATURE:
proposer_signatures.append(proposal.signature)

proposer_signature_aggregate = bls.Aggregate(proposer_signatures)
if len(proposer_signatures) > 0:
proposer_signature_aggregate = bls.Aggregate(proposer_signatures)
else:
proposer_signature_aggregate = NO_SIGNATURE

return ShardTransition(
start_slot=start_slot,
Expand Down
2 changes: 1 addition & 1 deletion tests/core/pyspec/eth2spec/test/helpers/keys.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
from eth2spec.phase0 import spec

privkeys = [i + 1 for i in range(spec.SLOTS_PER_EPOCH * 256)]
pubkeys = [bls.PrivToPub(privkey) for privkey in privkeys]
pubkeys = [bls.SkToPk(privkey) for privkey in privkeys]
pubkey_to_privkey = {pubkey: privkey for privkey, pubkey in zip(privkeys, pubkeys)}
30 changes: 25 additions & 5 deletions tests/core/pyspec/eth2spec/utils/bls.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,17 +25,32 @@ def entry(*args, **kw):

@only_with_bls(alt_return=True)
def Verify(PK, message, signature):
return bls.Verify(PK, message, signature)
try:
result = bls.Verify(PK, message, signature)
except Exception:
result = False
finally:
return result


@only_with_bls(alt_return=True)
def AggregateVerify(pairs, signature):
return bls.AggregateVerify(pairs, signature)
def AggregateVerify(pubkeys, messages, signature):
try:
result = bls.AggregateVerify(pubkeys, messages, signature)
except Exception:
result = False
finally:
return result


@only_with_bls(alt_return=True)
def FastAggregateVerify(PKs, message, signature):
return bls.FastAggregateVerify(PKs, message, signature)
def FastAggregateVerify(pubkeys, message, signature):
try:
result = bls.FastAggregateVerify(pubkeys, message, signature)
except Exception:
result = False
finally:
return result


@only_with_bls(alt_return=STUB_SIGNATURE)
Expand All @@ -56,3 +71,8 @@ def signature_to_G2(signature):
@only_with_bls(alt_return=STUB_PUBKEY)
def AggregatePKs(pubkeys):
return bls._AggregatePKs(pubkeys)


@only_with_bls(alt_return=STUB_SIGNATURE)
def SkToPk(SK):
return bls.SkToPk(SK)
11 changes: 5 additions & 6 deletions tests/formats/bls/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,10 @@ We do not recommend rolling your own crypto or using an untested BLS library.

The BLS test suite runner has the following handlers:

- [`aggregate_pubkeys`](./aggregate_pubkeys.md)
- [`aggregate_sigs`](./aggregate_sigs.md)
- [`msg_hash_g2_compressed`](./msg_hash_g2_compressed.md)
- [`msg_hash_g2_uncompressed`](./msg_hash_g2_uncompressed.md)
- [`priv_to_pub`](./priv_to_pub.md)
- [`sign_msg`](./sign_msg.md)
- [`aggregate_verify`](./aggregate_verify.md)
- [`aggregate`](./aggregate.md)
- [`fast_aggregate_verify`](./fast_aggregate_verify.md)
- [`sign`](./sign.md)
- [`verify`](./verify.md)

*Note*: Signature-verification and aggregate-verify test cases are not yet supported.
19 changes: 19 additions & 0 deletions tests/formats/bls/aggregate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Test format: BLS signature aggregation

A BLS signature aggregation combines a series of signatures into a single signature.

## Test case format

The test data is declared in a `data.yaml` file:

```yaml
input: List[BLS Signature] -- list of input BLS signatures
output: BLS Signature -- expected output, single BLS signature or empty.
```

- `BLS Signature` here is encoded as a string: hexadecimal encoding of 96 bytes (192 nibbles), prefixed with `0x`.
- No output value if the input is invalid.

## Condition

The `aggregate` handler should aggregate the signatures in the `input`, and the result should match the expected `output`.
19 changes: 0 additions & 19 deletions tests/formats/bls/aggregate_pubkeys.md

This file was deleted.

19 changes: 0 additions & 19 deletions tests/formats/bls/aggregate_sigs.md

This file was deleted.

17 changes: 17 additions & 0 deletions tests/formats/bls/aggregate_verify.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Test format: BLS sign message

Verify the signature against the given pubkeys and one messages.

## Test case format

The test data is declared in a `data.yaml` file:

```yaml
input:
pubkeys: List[bytes48] -- the pubkeys
messages: List[bytes32] -- the messages
signature: bytes96 -- the signature to verify against pubkeys and messages
output: bool -- VALID or INVALID
```

All byte(s) fields are encoded as strings, hexadecimal encoding, prefixed with `0x`.
17 changes: 17 additions & 0 deletions tests/formats/bls/fast_aggregate_verify.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Test format: BLS sign message

Verify the signature against the given pubkeys and one message.

## Test case format

The test data is declared in a `data.yaml` file:

```yaml
input:
pubkeys: List[bytes48] -- the pubkey
message: bytes32 -- the message
signature: bytes96 -- the signature to verify against pubkeys and message
output: bool -- VALID or INVALID
```

All byte(s) fields are encoded as strings, hexadecimal encoding, prefixed with `0x`.
21 changes: 0 additions & 21 deletions tests/formats/bls/msg_hash_g2_compressed.md

This file was deleted.

21 changes: 0 additions & 21 deletions tests/formats/bls/msg_hash_g2_uncompressed.md

This file was deleted.

19 changes: 0 additions & 19 deletions tests/formats/bls/priv_to_pub.md

This file was deleted.

6 changes: 0 additions & 6 deletions tests/formats/bls/sign_msg.md → tests/formats/bls/sign.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,7 @@ The test data is declared in a `data.yaml` file:
input:
privkey: bytes32 -- the private key used for signing
message: bytes32 -- input message to sign (a hash)
domain: bytes8 -- the BLS domain
output: bytes96 -- expected signature
```

All byte(s) fields are encoded as strings, hexadecimal encoding, prefixed with `0x`.


## Condition

The `sign_msg` handler should sign the given `message`, with `domain`, using the given `privkey`, and the result should match the expected `output`.
Loading