From d8d068640011541ef99fe29cc6438c1c986168ac Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Wed, 4 Aug 2021 01:55:18 +0800 Subject: [PATCH] Add tests for the Altair BLS helpers --- tests/formats/bls/README.md | 2 + tests/formats/bls/eth2_aggregate_pubkeys.md | 19 +++ .../formats/bls/eth2_fast_aggregate_verify.md | 17 +++ tests/formats/bls/fast_aggregate_verify.md | 2 +- tests/generators/bls/main.py | 144 +++++++++++++++++- 5 files changed, 176 insertions(+), 8 deletions(-) create mode 100644 tests/formats/bls/eth2_aggregate_pubkeys.md create mode 100644 tests/formats/bls/eth2_fast_aggregate_verify.md diff --git a/tests/formats/bls/README.md b/tests/formats/bls/README.md index 65154ba1c..65018631a 100644 --- a/tests/formats/bls/README.md +++ b/tests/formats/bls/README.md @@ -7,6 +7,8 @@ The BLS test suite runner has the following handlers: - [`aggregate_verify`](./aggregate_verify.md) - [`aggregate`](./aggregate.md) +- [`eth2_aggregate_pubkeys`](./eth2_aggregate_pubkeys.md) +- [`eth2_fast_aggregate_verify`](./eth2_fast_aggregate_verify.md) - [`fast_aggregate_verify`](./fast_aggregate_verify.md) - [`sign`](./sign.md) - [`verify`](./verify.md) diff --git a/tests/formats/bls/eth2_aggregate_pubkeys.md b/tests/formats/bls/eth2_aggregate_pubkeys.md new file mode 100644 index 000000000..dd35b3166 --- /dev/null +++ b/tests/formats/bls/eth2_aggregate_pubkeys.md @@ -0,0 +1,19 @@ +# Test format: Ethereum-customized BLS pubkeys aggregation + +A BLS pubkeys 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 pubkeys or empty. +``` + +- `BLS Pubkey` here is encoded as a string: hexadecimal encoding of 48 bytes (96 nibbles), prefixed with `0x`. +- No output value if the input is invalid. + +## Condition + +The `eth2_aggregate_pubkeys` handler should aggregate the signatures in the `input`, and the result should match the expected `output`. diff --git a/tests/formats/bls/eth2_fast_aggregate_verify.md b/tests/formats/bls/eth2_fast_aggregate_verify.md new file mode 100644 index 000000000..ddc1b5208 --- /dev/null +++ b/tests/formats/bls/eth2_fast_aggregate_verify.md @@ -0,0 +1,17 @@ +# Test format: Ethereum-customized BLS fast aggregate verify + +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`. diff --git a/tests/formats/bls/fast_aggregate_verify.md b/tests/formats/bls/fast_aggregate_verify.md index 7e3899a15..3366cbb79 100644 --- a/tests/formats/bls/fast_aggregate_verify.md +++ b/tests/formats/bls/fast_aggregate_verify.md @@ -1,4 +1,4 @@ -# Test format: BLS sign message +# Test format: BLS fast aggregate verify Verify the signature against the given pubkeys and one message. diff --git a/tests/generators/bls/main.py b/tests/generators/bls/main.py index 3ebaa1354..877324a2e 100644 --- a/tests/generators/bls/main.py +++ b/tests/generators/bls/main.py @@ -12,8 +12,10 @@ from eth_utils import ( import milagro_bls_binding as milagro_bls from eth2spec.utils import bls -from eth2spec.test.helpers.constants import PHASE0 +from eth2spec.test.helpers.constants import PHASE0, ALTAIR +from eth2spec.test.helpers.typing import SpecForkName from eth2spec.gen_helpers.gen_base import gen_runner, gen_typing +from eth2spec.altair import spec def to_bytes(i): @@ -51,6 +53,7 @@ PRIVKEYS = [ ] PUBKEYS = [bls.SkToPk(privkey) for privkey in PRIVKEYS] +ZERO_PUBKEY = b'\x00' * 48 Z1_PUBKEY = b'\xc0' + b'\x00' * 47 NO_SIGNATURE = b'\x00' * 96 Z2_SIGNATURE = b'\xc0' + b'\x00' * 95 @@ -355,7 +358,130 @@ def case05_aggregate_verify(): } +def case06_eth2_aggregate_pubkeys(): + aggregate_pubkey = spec.eth2_aggregate_pubkeys(PUBKEYS) + assert aggregate_pubkey == milagro_bls._AggregatePKs(PUBKEYS) + yield f'eth2_aggregate_pubkeys_some_pubkeys', { + 'input': [encode_hex(pubkey) for pubkey in PUBKEYS], + 'output': encode_hex(aggregate_pubkey), + } + + # Invalid pubkeys -- len(pubkeys) == 0 + expect_exception(spec.eth2_aggregate_pubkeys, []) + expect_exception(milagro_bls._AggregatePKs, []) + yield f'eth2_aggregate_pubkeys_', { + 'input': [], + 'output': None, + } + + # Invalid pubkeys -- [ZERO_PUBKEY] + expect_exception(spec.eth2_aggregate_pubkeys, [ZERO_PUBKEY]) + expect_exception(milagro_bls._AggregatePKs, [ZERO_PUBKEY]) + yield f'eth2_aggregate_pubkeys_all_zero_pubkey', { + 'input': [encode_hex(ZERO_PUBKEY)], + 'output': None, + } + + # TODO: TBD + # Valid to aggregate G1 point at infinity + # aggregate_pubkey = spec.eth2_aggregate_pubkeys([Z1_PUBKEY]) + # assert aggregate_pubkey == milagro_bls._AggregatePKs([Z1_PUBKEY]) == Z1_PUBKEY + # yield f'eth2_aggregate_pubkeys_infinity_pubkey', { + # 'input': [encode_hex(Z1_PUBKEY)], + # 'output': encode_hex(aggregate_pubkey), + # } + + +def case07_eth2_fast_aggregate_verify(): + """ + Similar to `case04_fast_aggregate_verify` except for the empty case + """ + for i, message in enumerate(MESSAGES): + privkeys = PRIVKEYS[:i + 1] + sigs = [bls.Sign(privkey, message) for privkey in privkeys] + aggregate_signature = bls.Aggregate(sigs) + pubkeys = [bls.SkToPk(privkey) for privkey in privkeys] + pubkeys_serial = [encode_hex(pubkey) for pubkey in pubkeys] + + # Valid signature + identifier = f'{pubkeys_serial}_{encode_hex(message)}' + assert spec.eth2_fast_aggregate_verify(pubkeys, message, aggregate_signature) + yield f'eth2_fast_aggregate_verify_valid_{(hash(bytes(identifier, "utf-8"))[:8]).hex()}', { + 'input': { + 'pubkeys': pubkeys_serial, + 'message': encode_hex(message), + 'signature': encode_hex(aggregate_signature), + }, + 'output': True, + } + + # Invalid signature -- extra pubkey + pubkeys_extra = pubkeys + [bls.SkToPk(PRIVKEYS[-1])] + pubkeys_extra_serial = [encode_hex(pubkey) for pubkey in pubkeys_extra] + identifier = f'{pubkeys_extra_serial}_{encode_hex(message)}' + assert not spec.eth2_fast_aggregate_verify(pubkeys_extra, message, aggregate_signature) + yield f'eth_fast_aggregate_verify_extra_pubkey_{(hash(bytes(identifier, "utf-8"))[:8]).hex()}', { + 'input': { + 'pubkeys': pubkeys_extra_serial, + 'message': encode_hex(message), + 'signature': encode_hex(aggregate_signature), + }, + 'output': False, + } + + # Invalid signature -- tampered with signature + tampered_signature = aggregate_signature[:-4] + b'\xff\xff\xff\xff' + identifier = f'{pubkeys_serial}_{encode_hex(message)}' + assert not spec.eth2_fast_aggregate_verify(pubkeys, message, tampered_signature) + yield f'eth2_fast_aggregate_verify_tampered_signature_{(hash(bytes(identifier, "utf-8"))[:8]).hex()}', { + 'input': { + 'pubkeys': pubkeys_serial, + 'message': encode_hex(message), + 'signature': encode_hex(tampered_signature), + }, + 'output': False, + } + + # NOTE: Unlike `FastAggregateVerify`, len(pubkeys) == 0 and signature == Z1_SIGNATURE is VALID + assert spec.eth2_fast_aggregate_verify([], message, Z2_SIGNATURE) + yield f'eth2_fast_aggregate_verify_na_pubkeys_and_infinity_signature', { + 'input': { + 'pubkeys': [], + 'message': encode_hex(message), + 'signature': encode_hex(Z2_SIGNATURE), + }, + 'output': True, + } + + # Invalid pubkeys and signature -- len(pubkeys) == 0 and signature == 0x00... + assert not spec.eth2_fast_aggregate_verify([], message, NO_SIGNATURE) + yield f'eth2_fast_aggregate_verify_na_pubkeys_and_na_signature', { + 'input': { + 'pubkeys': [], + 'message': encode_hex(message), + 'signature': encode_hex(NO_SIGNATURE), + }, + 'output': False, + } + + # Invalid pubkeys and signature -- pubkeys contains point at infinity + pubkeys = PUBKEYS.copy() + pubkeys_with_infinity = pubkeys + [Z1_PUBKEY] + signatures = [bls.Sign(privkey, SAMPLE_MESSAGE) for privkey in PRIVKEYS] + aggregate_signature = bls.Aggregate(signatures) + assert not spec.eth2_fast_aggregate_verify(pubkeys_with_infinity, SAMPLE_MESSAGE, aggregate_signature) + yield f'eth2_fast_aggregate_verify_infinity_pubkey', { + 'input': { + 'pubkeys': [encode_hex(pubkey) for pubkey in pubkeys_with_infinity], + 'message': encode_hex(SAMPLE_MESSAGE), + 'signature': encode_hex(aggregate_signature), + }, + 'output': False, + } + + def create_provider(handler_name: str, + fork_name: SpecForkName, test_case_fn: Callable[[], Iterable[Tuple[str, Dict[str, Any]]]]) -> gen_typing.TestProvider: def prepare_fn() -> None: @@ -368,7 +494,7 @@ def create_provider(handler_name: str, print(data) (case_name, case_content) = data yield gen_typing.TestCase( - fork_name=PHASE0, + fork_name=fork_name, preset_name='general', runner_name='bls', handler_name=handler_name, @@ -383,9 +509,13 @@ def create_provider(handler_name: str, if __name__ == "__main__": bls.use_py_ecc() # Py-ecc is chosen instead of Milagro, since the code is better understood to be correct. gen_runner.run_generator("bls", [ - create_provider('sign', case01_sign), - create_provider('verify', case02_verify), - create_provider('aggregate', case03_aggregate), - create_provider('fast_aggregate_verify', case04_fast_aggregate_verify), - create_provider('aggregate_verify', case05_aggregate_verify), + # PHASE0 + create_provider('sign', PHASE0, case01_sign), + create_provider('verify', PHASE0, case02_verify), + create_provider('aggregate', PHASE0, case03_aggregate), + create_provider('fast_aggregate_verify', PHASE0, case04_fast_aggregate_verify), + create_provider('aggregate_verify', PHASE0, case05_aggregate_verify), + # ALTAIR + create_provider('eth2_aggregate_pubkeys', ALTAIR, case06_eth2_aggregate_pubkeys), + create_provider('eth2_fast_aggregate_verify', ALTAIR, case07_eth2_fast_aggregate_verify), ])