Merge pull request #1799 from ethereum/bls_v2
Update to IETF BLS draft-irtf-cfrg-bls-signature-02 + draft-irtf-cfrg-hash-to-curve-07
This commit is contained in:
commit
4a86c39712
2
setup.py
2
setup.py
|
@ -501,7 +501,7 @@ setup(
|
||||||
"eth-utils>=1.3.0,<2",
|
"eth-utils>=1.3.0,<2",
|
||||||
"eth-typing>=2.1.0,<3.0.0",
|
"eth-typing>=2.1.0,<3.0.0",
|
||||||
"pycryptodome==3.9.4",
|
"pycryptodome==3.9.4",
|
||||||
"py_ecc==2.0.0",
|
"py_ecc==4.0.0",
|
||||||
"dataclasses==0.6",
|
"dataclasses==0.6",
|
||||||
"remerkleable==0.1.13",
|
"remerkleable==0.1.13",
|
||||||
"ruamel.yaml==0.16.5",
|
"ruamel.yaml==0.16.5",
|
||||||
|
|
|
@ -607,16 +607,18 @@ def bytes_to_int(data: bytes) -> uint64:
|
||||||
|
|
||||||
#### BLS Signatures
|
#### 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 Sign(SK: int, message: Bytes) -> BLSSignature`
|
||||||
- `def Verify(PK: BLSPubkey, message: Bytes, signature: BLSSignature) -> bool`
|
- `def Verify(PK: BLSPubkey, message: Bytes, signature: BLSSignature) -> bool`
|
||||||
- `def Aggregate(signatures: Sequence[BLSSignature]) -> BLSSignature`
|
- `def Aggregate(signatures: Sequence[BLSSignature]) -> BLSSignature`
|
||||||
- `def FastAggregateVerify(PKs: Sequence[BLSPubkey], message: Bytes, signature: BLSSignature) -> bool`
|
- `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.
|
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
|
### Predicates
|
||||||
|
|
||||||
#### `is_active_validator`
|
#### `is_active_validator`
|
||||||
|
|
|
@ -459,7 +459,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.
|
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
|
##### Data
|
||||||
|
|
||||||
|
|
|
@ -55,6 +55,8 @@
|
||||||
- [Updated `is_valid_indexed_attestation`](#updated-is_valid_indexed_attestation)
|
- [Updated `is_valid_indexed_attestation`](#updated-is_valid_indexed_attestation)
|
||||||
- [`is_shard_attestation`](#is_shard_attestation)
|
- [`is_shard_attestation`](#is_shard_attestation)
|
||||||
- [`is_winning_attestation`](#is_winning_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)
|
- [Block processing](#block-processing)
|
||||||
- [Operations](#operations)
|
- [Operations](#operations)
|
||||||
- [New Attestation processing](#new-attestation-processing)
|
- [New Attestation processing](#new-attestation-processing)
|
||||||
|
@ -110,6 +112,7 @@ Configuration is not namespaced. Instead it is strictly an extension;
|
||||||
| `DOMAIN_SHARD_PROPOSAL` | `DomainType('0x80000000')` | |
|
| `DOMAIN_SHARD_PROPOSAL` | `DomainType('0x80000000')` | |
|
||||||
| `DOMAIN_SHARD_COMMITTEE` | `DomainType('0x81000000')` | |
|
| `DOMAIN_SHARD_COMMITTEE` | `DomainType('0x81000000')` | |
|
||||||
| `DOMAIN_LIGHT_CLIENT` | `DomainType('0x82000000')` | |
|
| `DOMAIN_LIGHT_CLIENT` | `DomainType('0x82000000')` | |
|
||||||
|
| `NO_SIGNATURE` | `BLSSignature(b'\x00' * 96)` | |
|
||||||
|
|
||||||
## Updated containers
|
## Updated containers
|
||||||
|
|
||||||
|
@ -596,7 +599,7 @@ def is_valid_indexed_attestation(state: BeaconState, indexed_attestation: Indexe
|
||||||
all_signing_roots.append(compute_signing_root(attestation_wrapper, domain))
|
all_signing_roots.append(compute_signing_root(attestation_wrapper, domain))
|
||||||
else:
|
else:
|
||||||
assert not cbit
|
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`
|
#### `is_shard_attestation`
|
||||||
|
@ -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
|
### Block processing
|
||||||
|
|
||||||
```python
|
```python
|
||||||
|
@ -764,7 +797,7 @@ def apply_shard_transition(state: BeaconState, shard: Shard, transition: ShardTr
|
||||||
for header in headers
|
for header in headers
|
||||||
]
|
]
|
||||||
# Verify combined proposer signature
|
# 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
|
# Save updated state
|
||||||
state.shard_states[shard] = transition.shard_states[len(transition.shard_states) - 1]
|
state.shard_states[shard] = transition.shard_states[len(transition.shard_states) - 1]
|
||||||
|
@ -942,12 +975,7 @@ def process_light_client_signatures(state: BeaconState, block_body: BeaconBlockB
|
||||||
slot = compute_previous_slot(state.slot)
|
slot = compute_previous_slot(state.slot)
|
||||||
signing_root = compute_signing_root(get_block_root_at_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)))
|
get_domain(state, DOMAIN_LIGHT_CLIENT, compute_epoch_at_slot(slot)))
|
||||||
if len(signer_pubkeys) == 0:
|
assert optional_fast_aggregate_verify(signer_pubkeys, signing_root, block_body.light_client_signature)
|
||||||
# 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)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Epoch transition
|
### Epoch transition
|
||||||
|
|
|
@ -300,7 +300,7 @@ def process_early_derived_secret_reveal(state: BeaconState, reveal: EarlyDerived
|
||||||
|
|
||||||
domain = get_domain(state, DOMAIN_RANDAO, reveal.epoch)
|
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]]
|
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:
|
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
|
# Full slashing when the secret was revealed so early it may be a valid custody
|
||||||
|
|
|
@ -277,10 +277,13 @@ def get_shard_transition(beacon_state: BeaconState,
|
||||||
proposer_signatures = []
|
proposer_signatures = []
|
||||||
for proposal in proposals:
|
for proposal in proposals:
|
||||||
shard_block_lengths.append(len(proposal.message.body))
|
shard_block_lengths.append(len(proposal.message.body))
|
||||||
if proposal.signature != BLSSignature():
|
if proposal.signature != NO_SIGNATURE:
|
||||||
proposer_signatures.append(proposal.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(
|
return ShardTransition(
|
||||||
start_slot=start_slot,
|
start_slot=start_slot,
|
||||||
|
|
|
@ -2,5 +2,5 @@ from py_ecc.bls import G2ProofOfPossession as bls
|
||||||
from eth2spec.phase0 import spec
|
from eth2spec.phase0 import spec
|
||||||
|
|
||||||
privkeys = [i + 1 for i in range(spec.SLOTS_PER_EPOCH * 256)]
|
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)}
|
pubkey_to_privkey = {pubkey: privkey for privkey, pubkey in zip(privkeys, pubkeys)}
|
||||||
|
|
|
@ -25,17 +25,32 @@ def only_with_bls(alt_return=None):
|
||||||
|
|
||||||
@only_with_bls(alt_return=True)
|
@only_with_bls(alt_return=True)
|
||||||
def Verify(PK, message, signature):
|
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)
|
@only_with_bls(alt_return=True)
|
||||||
def AggregateVerify(pairs, signature):
|
def AggregateVerify(pubkeys, messages, signature):
|
||||||
return bls.AggregateVerify(pairs, signature)
|
try:
|
||||||
|
result = bls.AggregateVerify(pubkeys, messages, signature)
|
||||||
|
except Exception:
|
||||||
|
result = False
|
||||||
|
finally:
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
@only_with_bls(alt_return=True)
|
@only_with_bls(alt_return=True)
|
||||||
def FastAggregateVerify(PKs, message, signature):
|
def FastAggregateVerify(pubkeys, message, signature):
|
||||||
return bls.FastAggregateVerify(PKs, message, signature)
|
try:
|
||||||
|
result = bls.FastAggregateVerify(pubkeys, message, signature)
|
||||||
|
except Exception:
|
||||||
|
result = False
|
||||||
|
finally:
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
@only_with_bls(alt_return=STUB_SIGNATURE)
|
@only_with_bls(alt_return=STUB_SIGNATURE)
|
||||||
|
@ -56,3 +71,8 @@ def signature_to_G2(signature):
|
||||||
@only_with_bls(alt_return=STUB_PUBKEY)
|
@only_with_bls(alt_return=STUB_PUBKEY)
|
||||||
def AggregatePKs(pubkeys):
|
def AggregatePKs(pubkeys):
|
||||||
return bls._AggregatePKs(pubkeys)
|
return bls._AggregatePKs(pubkeys)
|
||||||
|
|
||||||
|
|
||||||
|
@only_with_bls(alt_return=STUB_SIGNATURE)
|
||||||
|
def SkToPk(SK):
|
||||||
|
return bls.SkToPk(SK)
|
||||||
|
|
|
@ -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:
|
The BLS test suite runner has the following handlers:
|
||||||
|
|
||||||
- [`aggregate_pubkeys`](./aggregate_pubkeys.md)
|
- [`aggregate_verify`](./aggregate_verify.md)
|
||||||
- [`aggregate_sigs`](./aggregate_sigs.md)
|
- [`aggregate`](./aggregate.md)
|
||||||
- [`msg_hash_g2_compressed`](./msg_hash_g2_compressed.md)
|
- [`fast_aggregate_verify`](./fast_aggregate_verify.md)
|
||||||
- [`msg_hash_g2_uncompressed`](./msg_hash_g2_uncompressed.md)
|
- [`sign`](./sign.md)
|
||||||
- [`priv_to_pub`](./priv_to_pub.md)
|
- [`verify`](./verify.md)
|
||||||
- [`sign_msg`](./sign_msg.md)
|
|
||||||
|
|
||||||
*Note*: Signature-verification and aggregate-verify test cases are not yet supported.
|
*Note*: Signature-verification and aggregate-verify test cases are not yet supported.
|
||||||
|
|
|
@ -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`.
|
|
@ -1,19 +0,0 @@
|
||||||
# Test format: BLS pubkey aggregation
|
|
||||||
|
|
||||||
A BLS pubkey aggregation combines a series of pubkeys into a single pubkey.
|
|
||||||
|
|
||||||
## Test case format
|
|
||||||
|
|
||||||
The test data is declared in a `data.yaml` file:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
input: List[BLS Pubkey] -- list of input BLS pubkeys
|
|
||||||
output: BLS Pubkey -- expected output, single BLS pubkey
|
|
||||||
```
|
|
||||||
|
|
||||||
`BLS Pubkey` here is encoded as a string: hexadecimal encoding of 48 bytes (96 nibbles), prefixed with `0x`.
|
|
||||||
|
|
||||||
|
|
||||||
## Condition
|
|
||||||
|
|
||||||
The `aggregate_pubkeys` handler should aggregate the keys in the `input`, and the result should match the expected `output`.
|
|
|
@ -1,19 +0,0 @@
|
||||||
# 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
|
|
||||||
```
|
|
||||||
|
|
||||||
`BLS Signature` here is encoded as a string: hexadecimal encoding of 96 bytes (192 nibbles), prefixed with `0x`.
|
|
||||||
|
|
||||||
|
|
||||||
## Condition
|
|
||||||
|
|
||||||
The `aggregate_sigs` handler should aggregate the signatures in the `input`, and the result should match the expected `output`.
|
|
|
@ -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`.
|
|
@ -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`.
|
|
@ -1,21 +0,0 @@
|
||||||
# Test format: BLS hash-compressed
|
|
||||||
|
|
||||||
A BLS compressed-hash to G2.
|
|
||||||
|
|
||||||
## Test case format
|
|
||||||
|
|
||||||
The test data is declared in a `data.yaml` file:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
input:
|
|
||||||
message: bytes32
|
|
||||||
domain: bytes8 -- the BLS domain
|
|
||||||
output: List[bytes48] -- length of two
|
|
||||||
```
|
|
||||||
|
|
||||||
All byte(s) fields are encoded as strings, hexadecimal encoding, prefixed with `0x`.
|
|
||||||
|
|
||||||
|
|
||||||
## Condition
|
|
||||||
|
|
||||||
The `msg_hash_g2_compressed` handler should hash the `message`, with the given `domain`, to G2 with compression, and the result should match the expected `output`.
|
|
|
@ -1,21 +0,0 @@
|
||||||
# Test format: BLS hash-uncompressed
|
|
||||||
|
|
||||||
A BLS uncompressed-hash to G2.
|
|
||||||
|
|
||||||
## Test case format
|
|
||||||
|
|
||||||
The test data is declared in a `data.yaml` file:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
input:
|
|
||||||
message: bytes32
|
|
||||||
domain: bytes8 -- the BLS domain
|
|
||||||
output: List[List[bytes48]] -- 3 lists, each a length of two
|
|
||||||
```
|
|
||||||
|
|
||||||
All byte(s) fields are encoded as strings, hexadecimal encoding, prefixed with `0x`.
|
|
||||||
|
|
||||||
|
|
||||||
## Condition
|
|
||||||
|
|
||||||
The `msg_hash_g2_uncompressed` handler should hash the `message`, with the given `domain`, to G2, without compression, and the result should match the expected `output`.
|
|
|
@ -1,19 +0,0 @@
|
||||||
# Test format: BLS private key to pubkey
|
|
||||||
|
|
||||||
A BLS private key to public key conversion.
|
|
||||||
|
|
||||||
## Test case format
|
|
||||||
|
|
||||||
The test data is declared in a `data.yaml` file:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
input: bytes32 -- the private key
|
|
||||||
output: bytes48 -- the public key
|
|
||||||
```
|
|
||||||
|
|
||||||
All byte(s) fields are encoded as strings, hexadecimal encoding, prefixed with `0x`.
|
|
||||||
|
|
||||||
|
|
||||||
## Condition
|
|
||||||
|
|
||||||
The `priv_to_pub` handler should compute the public key for the given private key `input`, and the result should match the expected `output`.
|
|
|
@ -10,13 +10,7 @@ The test data is declared in a `data.yaml` file:
|
||||||
input:
|
input:
|
||||||
privkey: bytes32 -- the private key used for signing
|
privkey: bytes32 -- the private key used for signing
|
||||||
message: bytes32 -- input message to sign (a hash)
|
message: bytes32 -- input message to sign (a hash)
|
||||||
domain: bytes8 -- the BLS domain
|
|
||||||
output: bytes96 -- expected signature
|
output: bytes96 -- expected signature
|
||||||
```
|
```
|
||||||
|
|
||||||
All byte(s) fields are encoded as strings, hexadecimal encoding, prefixed with `0x`.
|
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`.
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
# Test format: BLS sign message
|
||||||
|
|
||||||
|
Verify the signature against the given one pubkey and one message.
|
||||||
|
|
||||||
|
## Test case format
|
||||||
|
|
||||||
|
The test data is declared in a `data.yaml` file:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
input:
|
||||||
|
pubkey: bytes48 -- the pubkey
|
||||||
|
message: bytes32 -- the message
|
||||||
|
signature: bytes96 -- the signature to verify against pubkey and message
|
||||||
|
output: bool -- VALID or INVALID
|
||||||
|
```
|
||||||
|
|
||||||
|
All byte(s) fields are encoded as strings, hexadecimal encoding, prefixed with `0x`.
|
|
@ -1,21 +1,11 @@
|
||||||
# BLS Test Generator
|
# BLS Test Generator
|
||||||
|
|
||||||
Explanation of BLS12-381 type hierarchy
|
The [BLS Signature APIs](../../../specs/phase0/beacon-chain.md#bls-signatures)
|
||||||
The base unit is bytes48 of which only 381 bits are used
|
|
||||||
|
|
||||||
- FQ: uint381 modulo field modulus
|
Information on the format of the tests can be found in the [BLS test formats documentation](../../formats/bls/README.md).
|
||||||
- FQ2: (FQ, FQ)
|
|
||||||
- G2: (FQ2, FQ2, FQ2)
|
|
||||||
|
|
||||||
## Resources
|
## Resources
|
||||||
|
|
||||||
- [Eth2 spec](../../../specs/phase0/beacon-chain.md#bls-signatures)
|
- [IETF BLS Signature Scheme](https://datatracker.ietf.org/doc/draft-irtf-cfrg-bls-signature/)
|
||||||
- [Finite Field Arithmetic](http://www.springeronline.com/sgw/cda/pageitems/document/cda_downloaddocument/0,11996,0-0-45-110359-0,00.pdf)
|
- [Finite Field Arithmetic](http://www.springeronline.com/sgw/cda/pageitems/document/cda_downloaddocument/0,11996,0-0-45-110359-0,00.pdf)
|
||||||
- Chapter 2 of [Elliptic Curve Cryptography](http://cacr.uwaterloo.ca/ecc/). Darrel Hankerson, Alfred Menezes, and Scott Vanstone
|
- Chapter 2 of [Elliptic Curve Cryptography](http://cacr.uwaterloo.ca/ecc/). Darrel Hankerson, Alfred Menezes, and Scott Vanstone
|
||||||
- [Zcash BLS parameters](https://github.com/zkcrypto/pairing/tree/master/src/bls12_381)
|
|
||||||
- [Trinity implementation](https://github.com/ethereum/trinity/blob/master/eth2/_utils/bls.py)
|
|
||||||
|
|
||||||
## Comments
|
|
||||||
|
|
||||||
Compared to Zcash, Ethereum specs always requires the compressed form (c_flag / most significant bit always set).
|
|
||||||
Also note that pubkeys and privkeys are reversed.
|
|
||||||
|
|
|
@ -10,20 +10,16 @@ from eth_utils import (
|
||||||
)
|
)
|
||||||
from gen_base import gen_runner, gen_typing
|
from gen_base import gen_runner, gen_typing
|
||||||
|
|
||||||
from py_ecc import bls
|
from eth2spec.utils import bls
|
||||||
from hashlib import sha256
|
from hashlib import sha256
|
||||||
|
|
||||||
from eth2spec.test.context import PHASE0
|
from eth2spec.test.context import PHASE0
|
||||||
|
|
||||||
|
|
||||||
def hash(x):
|
def hash(x):
|
||||||
return sha256(x).digest()
|
return sha256(x).digest()
|
||||||
|
|
||||||
|
|
||||||
F2Q_COEFF_LEN = 48
|
|
||||||
G2_COMPRESSED_Z_LEN = 48
|
|
||||||
DST = bls.G2ProofOfPossession.DST
|
|
||||||
|
|
||||||
|
|
||||||
def int_to_hex(n: int, byte_length: int = None) -> str:
|
def int_to_hex(n: int, byte_length: int = None) -> str:
|
||||||
byte_value = int_to_big_endian(n)
|
byte_value = int_to_big_endian(n)
|
||||||
if byte_length:
|
if byte_length:
|
||||||
|
@ -49,11 +45,15 @@ PRIVKEYS = [
|
||||||
hex_to_int('0x00000000000000000000000000000000328388aff0d4a5b7dc9205abd374e7e98f3cd9f3418edb4eafda5fb16473d216'),
|
hex_to_int('0x00000000000000000000000000000000328388aff0d4a5b7dc9205abd374e7e98f3cd9f3418edb4eafda5fb16473d216'),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
Z1_PUBKEY = b'\xc0' + b'\x00' * 47
|
||||||
|
NO_SIGNATURE = b'\x00' * 96
|
||||||
|
Z2_SIGNATURE = b'\xc0' + b'\x00' * 95
|
||||||
|
|
||||||
|
|
||||||
def case01_sign():
|
def case01_sign():
|
||||||
for privkey in PRIVKEYS:
|
for privkey in PRIVKEYS:
|
||||||
for message in MESSAGES:
|
for message in MESSAGES:
|
||||||
sig = bls.G2ProofOfPossession.Sign(privkey, message)
|
sig = bls.Sign(privkey, message)
|
||||||
identifier = f'{int_to_hex(privkey)}_{encode_hex(message)}'
|
identifier = f'{int_to_hex(privkey)}_{encode_hex(message)}'
|
||||||
yield f'sign_case_{(hash(bytes(identifier, "utf-8"))[:8]).hex()}', {
|
yield f'sign_case_{(hash(bytes(identifier, "utf-8"))[:8]).hex()}', {
|
||||||
'input': {
|
'input': {
|
||||||
|
@ -68,9 +68,10 @@ def case02_verify():
|
||||||
for i, privkey in enumerate(PRIVKEYS):
|
for i, privkey in enumerate(PRIVKEYS):
|
||||||
for message in MESSAGES:
|
for message in MESSAGES:
|
||||||
# Valid signature
|
# Valid signature
|
||||||
signature = bls.G2ProofOfPossession.Sign(privkey, message)
|
signature = bls.Sign(privkey, message)
|
||||||
pubkey = bls.G2ProofOfPossession.PrivToPub(privkey)
|
pubkey = bls.SkToPk(privkey)
|
||||||
identifier = f'{encode_hex(pubkey)}_{encode_hex(message)}'
|
identifier = f'{encode_hex(pubkey)}_{encode_hex(message)}'
|
||||||
|
assert bls.Verify(pubkey, message, signature)
|
||||||
yield f'verify_valid_case_{(hash(bytes(identifier, "utf-8"))[:8]).hex()}', {
|
yield f'verify_valid_case_{(hash(bytes(identifier, "utf-8"))[:8]).hex()}', {
|
||||||
'input': {
|
'input': {
|
||||||
'pubkey': encode_hex(pubkey),
|
'pubkey': encode_hex(pubkey),
|
||||||
|
@ -81,8 +82,9 @@ def case02_verify():
|
||||||
}
|
}
|
||||||
|
|
||||||
# Invalid signatures -- wrong pubkey
|
# Invalid signatures -- wrong pubkey
|
||||||
wrong_pubkey = bls.G2ProofOfPossession.PrivToPub(PRIVKEYS[(i + 1) % len(PRIVKEYS)])
|
wrong_pubkey = bls.SkToPk(PRIVKEYS[(i + 1) % len(PRIVKEYS)])
|
||||||
identifier = f'{encode_hex(wrong_pubkey)}_{encode_hex(message)}'
|
identifier = f'{encode_hex(wrong_pubkey)}_{encode_hex(message)}'
|
||||||
|
assert not bls.Verify(wrong_pubkey, message, signature)
|
||||||
yield f'verify_wrong_pubkey_case_{(hash(bytes(identifier, "utf-8"))[:8]).hex()}', {
|
yield f'verify_wrong_pubkey_case_{(hash(bytes(identifier, "utf-8"))[:8]).hex()}', {
|
||||||
'input': {
|
'input': {
|
||||||
'pubkey': encode_hex(wrong_pubkey),
|
'pubkey': encode_hex(wrong_pubkey),
|
||||||
|
@ -95,6 +97,7 @@ def case02_verify():
|
||||||
# Invalid signature -- tampered with signature
|
# Invalid signature -- tampered with signature
|
||||||
tampered_signature = signature[:-4] + b'\xFF\xFF\xFF\xFF'
|
tampered_signature = signature[:-4] + b'\xFF\xFF\xFF\xFF'
|
||||||
identifier = f'{encode_hex(pubkey)}_{encode_hex(message)}'
|
identifier = f'{encode_hex(pubkey)}_{encode_hex(message)}'
|
||||||
|
assert not bls.Verify(pubkey, message, tampered_signature)
|
||||||
yield f'verify_tampered_signature_case_{(hash(bytes(identifier, "utf-8"))[:8]).hex()}', {
|
yield f'verify_tampered_signature_case_{(hash(bytes(identifier, "utf-8"))[:8]).hex()}', {
|
||||||
'input': {
|
'input': {
|
||||||
'pubkey': encode_hex(pubkey),
|
'pubkey': encode_hex(pubkey),
|
||||||
|
@ -104,26 +107,51 @@ def case02_verify():
|
||||||
'output': False,
|
'output': False,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Valid pubkey and signature with the point at infinity
|
||||||
|
assert bls.Verify(Z1_PUBKEY, message, Z2_SIGNATURE)
|
||||||
|
yield f'verify_infinity_pubkey_and_infinity_signature', {
|
||||||
|
'input': {
|
||||||
|
'pubkey': encode_hex(Z1_PUBKEY),
|
||||||
|
'message': encode_hex(message),
|
||||||
|
'signature': encode_hex(Z2_SIGNATURE),
|
||||||
|
},
|
||||||
|
'output': True,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def case03_aggregate():
|
def case03_aggregate():
|
||||||
for message in MESSAGES:
|
for message in MESSAGES:
|
||||||
sigs = [bls.G2ProofOfPossession.Sign(privkey, message) for privkey in PRIVKEYS]
|
sigs = [bls.Sign(privkey, message) for privkey in PRIVKEYS]
|
||||||
yield f'aggregate_{encode_hex(message)}', {
|
yield f'aggregate_{encode_hex(message)}', {
|
||||||
'input': [encode_hex(sig) for sig in sigs],
|
'input': [encode_hex(sig) for sig in sigs],
|
||||||
'output': encode_hex(bls.G2ProofOfPossession.Aggregate(sigs)),
|
'output': encode_hex(bls.Aggregate(sigs)),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Invalid pubkeys -- len(pubkeys) == 0
|
||||||
|
try:
|
||||||
|
bls.Aggregate([])
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
raise Exception("Should have been INVALID")
|
||||||
|
|
||||||
|
yield f'aggregate_na_pubkeys', {
|
||||||
|
'input': [],
|
||||||
|
'output': None,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def case04_fast_aggregate_verify():
|
def case04_fast_aggregate_verify():
|
||||||
for i, message in enumerate(MESSAGES):
|
for i, message in enumerate(MESSAGES):
|
||||||
privkeys = PRIVKEYS[:i + 1]
|
privkeys = PRIVKEYS[:i + 1]
|
||||||
sigs = [bls.G2ProofOfPossession.Sign(privkey, message) for privkey in privkeys]
|
sigs = [bls.Sign(privkey, message) for privkey in privkeys]
|
||||||
aggregate_signature = bls.G2ProofOfPossession.Aggregate(sigs)
|
aggregate_signature = bls.Aggregate(sigs)
|
||||||
pubkeys = [bls.G2ProofOfPossession.PrivToPub(privkey) for privkey in privkeys]
|
pubkeys = [bls.SkToPk(privkey) for privkey in privkeys]
|
||||||
pubkeys_serial = [encode_hex(pubkey) for pubkey in pubkeys]
|
pubkeys_serial = [encode_hex(pubkey) for pubkey in pubkeys]
|
||||||
|
|
||||||
# Valid signature
|
# Valid signature
|
||||||
identifier = f'{pubkeys_serial}_{encode_hex(message)}'
|
identifier = f'{pubkeys_serial}_{encode_hex(message)}'
|
||||||
|
assert bls.FastAggregateVerify(pubkeys, message, aggregate_signature)
|
||||||
yield f'fast_aggregate_verify_valid_{(hash(bytes(identifier, "utf-8"))[:8]).hex()}', {
|
yield f'fast_aggregate_verify_valid_{(hash(bytes(identifier, "utf-8"))[:8]).hex()}', {
|
||||||
'input': {
|
'input': {
|
||||||
'pubkeys': pubkeys_serial,
|
'pubkeys': pubkeys_serial,
|
||||||
|
@ -134,9 +162,10 @@ def case04_fast_aggregate_verify():
|
||||||
}
|
}
|
||||||
|
|
||||||
# Invalid signature -- extra pubkey
|
# Invalid signature -- extra pubkey
|
||||||
pubkeys_extra = pubkeys + [bls.G2ProofOfPossession.PrivToPub(PRIVKEYS[-1])]
|
pubkeys_extra = pubkeys + [bls.SkToPk(PRIVKEYS[-1])]
|
||||||
pubkeys_extra_serial = [encode_hex(pubkey) for pubkey in pubkeys_extra]
|
pubkeys_extra_serial = [encode_hex(pubkey) for pubkey in pubkeys_extra]
|
||||||
identifier = f'{pubkeys_extra_serial}_{encode_hex(message)}'
|
identifier = f'{pubkeys_extra_serial}_{encode_hex(message)}'
|
||||||
|
assert not bls.FastAggregateVerify(pubkeys_extra, message, aggregate_signature)
|
||||||
yield f'fast_aggregate_verify_extra_pubkey_{(hash(bytes(identifier, "utf-8"))[:8]).hex()}', {
|
yield f'fast_aggregate_verify_extra_pubkey_{(hash(bytes(identifier, "utf-8"))[:8]).hex()}', {
|
||||||
'input': {
|
'input': {
|
||||||
'pubkeys': pubkeys_extra_serial,
|
'pubkeys': pubkeys_extra_serial,
|
||||||
|
@ -149,6 +178,7 @@ def case04_fast_aggregate_verify():
|
||||||
# Invalid signature -- tampered with signature
|
# Invalid signature -- tampered with signature
|
||||||
tampered_signature = aggregate_signature[:-4] + b'\xff\xff\xff\xff'
|
tampered_signature = aggregate_signature[:-4] + b'\xff\xff\xff\xff'
|
||||||
identifier = f'{pubkeys_serial}_{encode_hex(message)}'
|
identifier = f'{pubkeys_serial}_{encode_hex(message)}'
|
||||||
|
assert not bls.FastAggregateVerify(pubkeys, message, tampered_signature)
|
||||||
yield f'fast_aggregate_verify_tampered_signature_{(hash(bytes(identifier, "utf-8"))[:8]).hex()}', {
|
yield f'fast_aggregate_verify_tampered_signature_{(hash(bytes(identifier, "utf-8"))[:8]).hex()}', {
|
||||||
'input': {
|
'input': {
|
||||||
'pubkeys': pubkeys_serial,
|
'pubkeys': pubkeys_serial,
|
||||||
|
@ -158,37 +188,88 @@ def case04_fast_aggregate_verify():
|
||||||
'output': False,
|
'output': False,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Invalid pubkeys and signature -- len(pubkeys) == 0 and signature == Z1_SIGNATURE
|
||||||
|
assert not bls.FastAggregateVerify([], message, Z2_SIGNATURE)
|
||||||
|
yield f'fast_aggregate_verify_na_pubkeys_and_infinity_signature', {
|
||||||
|
'input': {
|
||||||
|
'pubkeys': [],
|
||||||
|
'message': encode_hex(message),
|
||||||
|
'signature': encode_hex(Z2_SIGNATURE),
|
||||||
|
},
|
||||||
|
'output': False,
|
||||||
|
}
|
||||||
|
|
||||||
|
# Invalid pubkeys and signature -- len(pubkeys) == 0 and signature == 0x00...
|
||||||
|
assert not bls.FastAggregateVerify([], message, NO_SIGNATURE)
|
||||||
|
yield f'fast_aggregate_verify_na_pubkeys_and_na_signature', {
|
||||||
|
'input': {
|
||||||
|
'pubkeys': [],
|
||||||
|
'message': encode_hex(message),
|
||||||
|
'signature': encode_hex(NO_SIGNATURE),
|
||||||
|
},
|
||||||
|
'output': False,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def case05_aggregate_verify():
|
def case05_aggregate_verify():
|
||||||
pairs = []
|
pubkeys = []
|
||||||
|
pubkeys_serial = []
|
||||||
|
messages = []
|
||||||
|
messages_serial = []
|
||||||
sigs = []
|
sigs = []
|
||||||
for privkey, message in zip(PRIVKEYS, MESSAGES):
|
for privkey, message in zip(PRIVKEYS, MESSAGES):
|
||||||
sig = bls.G2ProofOfPossession.Sign(privkey, message)
|
sig = bls.Sign(privkey, message)
|
||||||
pubkey = bls.G2ProofOfPossession.PrivToPub(privkey)
|
pubkey = bls.SkToPk(privkey)
|
||||||
pairs.append({
|
pubkeys.append(pubkey)
|
||||||
'pubkey': encode_hex(pubkey),
|
pubkeys_serial.append(encode_hex(pubkey))
|
||||||
'message': encode_hex(message),
|
messages.append(message)
|
||||||
})
|
messages_serial.append(encode_hex(message))
|
||||||
sigs.append(sig)
|
sigs.append(sig)
|
||||||
|
|
||||||
aggregate_signature = bls.G2ProofOfPossession.Aggregate(sigs)
|
aggregate_signature = bls.Aggregate(sigs)
|
||||||
|
assert bls.AggregateVerify(pubkeys, messages, aggregate_signature)
|
||||||
yield f'aggregate_verify_valid', {
|
yield f'aggregate_verify_valid', {
|
||||||
'input': {
|
'input': {
|
||||||
'pairs': pairs,
|
'pubkeys': pubkeys_serial,
|
||||||
|
'messages': messages_serial,
|
||||||
'signature': encode_hex(aggregate_signature),
|
'signature': encode_hex(aggregate_signature),
|
||||||
},
|
},
|
||||||
'output': True,
|
'output': True,
|
||||||
}
|
}
|
||||||
|
|
||||||
tampered_signature = aggregate_signature[:4] + b'\xff\xff\xff\xff'
|
tampered_signature = aggregate_signature[:4] + b'\xff\xff\xff\xff'
|
||||||
|
assert not bls.AggregateVerify(pubkey, messages, tampered_signature)
|
||||||
yield f'aggregate_verify_tampered_signature', {
|
yield f'aggregate_verify_tampered_signature', {
|
||||||
'input': {
|
'input': {
|
||||||
'pairs': pairs,
|
'pubkeys': pubkeys_serial,
|
||||||
|
'messages': messages_serial,
|
||||||
'signature': encode_hex(tampered_signature),
|
'signature': encode_hex(tampered_signature),
|
||||||
},
|
},
|
||||||
'output': False,
|
'output': False,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Invalid pubkeys and signature -- len(pubkeys) == 0 and signature == Z1_SIGNATURE
|
||||||
|
assert not bls.AggregateVerify([], [], Z2_SIGNATURE)
|
||||||
|
yield f'aggregate_verify_na_pubkeys_and_infinity_signature', {
|
||||||
|
'input': {
|
||||||
|
'pubkeys': [],
|
||||||
|
'messages': [],
|
||||||
|
'signature': encode_hex(Z2_SIGNATURE),
|
||||||
|
},
|
||||||
|
'output': False,
|
||||||
|
}
|
||||||
|
|
||||||
|
# Invalid pubkeys and signature -- len(pubkeys) == 0 and signature == 0x00...
|
||||||
|
assert not bls.AggregateVerify([], [], NO_SIGNATURE)
|
||||||
|
yield f'aggregate_verify_na_pubkeys_and_na_signature', {
|
||||||
|
'input': {
|
||||||
|
'pubkeys': [],
|
||||||
|
'messages': [],
|
||||||
|
'signature': encode_hex(NO_SIGNATURE),
|
||||||
|
},
|
||||||
|
'output': False,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def create_provider(handler_name: str,
|
def create_provider(handler_name: str,
|
||||||
test_case_fn: Callable[[], Iterable[Tuple[str, Dict[str, Any]]]]) -> gen_typing.TestProvider:
|
test_case_fn: Callable[[], Iterable[Tuple[str, Dict[str, Any]]]]) -> gen_typing.TestProvider:
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
py_ecc==2.0.0
|
py_ecc==4.0.0
|
||||||
eth-utils==1.6.0
|
eth-utils==1.6.0
|
||||||
../../core/gen_helpers
|
../../core/gen_helpers
|
||||||
../../../
|
../../../
|
||||||
|
|
Loading…
Reference in New Issue