Merge pull request #1813 from ethereum/bls_v2_tests

Update BLS test suite to BLS standard draft v2 format
This commit is contained in:
Danny Ryan 2020-05-15 16:49:22 -06:00 committed by GitHub
commit 4ffafa56dd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 209 additions and 154 deletions

View File

@ -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(pubkeys, messages, signature): def AggregateVerify(pubkeys, messages, signature):
return bls.AggregateVerify(pubkeys, messages, 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(pubkeys, message, signature): def FastAggregateVerify(pubkeys, message, signature):
return bls.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) @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)

View File

@ -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.

View File

@ -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`.

View File

@ -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`.

View File

@ -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`.

View File

@ -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`.

View File

@ -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`.

View File

@ -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`.

View File

@ -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`.

View File

@ -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`.

View File

@ -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`.

View File

@ -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`.

View File

@ -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.

View File

@ -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.SkToPk(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.SkToPk(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.SkToPk(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.SkToPk(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.SkToPk(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: