From 19d6c8336a446aa09677e0b3bc8af04f64556ad2 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Fri, 25 Sep 2020 14:37:24 +0800 Subject: [PATCH 1/6] Add IETF BLS draft 04 edge cases test vectors --- tests/core/pyspec/eth2spec/utils/bls.py | 5 +- tests/formats/bls/sign.md | 2 +- tests/generators/bls/main.py | 87 ++++++++++++++++++++----- 3 files changed, 74 insertions(+), 20 deletions(-) diff --git a/tests/core/pyspec/eth2spec/utils/bls.py b/tests/core/pyspec/eth2spec/utils/bls.py index 8b91dd64e..dd3172b4b 100644 --- a/tests/core/pyspec/eth2spec/utils/bls.py +++ b/tests/core/pyspec/eth2spec/utils/bls.py @@ -99,4 +99,7 @@ def AggregatePKs(pubkeys): @only_with_bls(alt_return=STUB_SIGNATURE) def SkToPk(SK): - return bls.SkToPk(SK) + if bls == py_ecc_bls: + return bls.SkToPk(SK) + else: + return bls.SkToPk(SK.to_bytes(32, 'big')) diff --git a/tests/formats/bls/sign.md b/tests/formats/bls/sign.md index 1c328755a..93001beee 100644 --- a/tests/formats/bls/sign.md +++ b/tests/formats/bls/sign.md @@ -10,7 +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) -output: bytes96 -- expected signature +output: BLS Signature -- expected output, single BLS signature or empty. ``` All byte(s) fields are encoded as strings, hexadecimal encoding, prefixed with `0x`. diff --git a/tests/generators/bls/main.py b/tests/generators/bls/main.py index 6fec61de0..93121f2a0 100644 --- a/tests/generators/bls/main.py +++ b/tests/generators/bls/main.py @@ -40,6 +40,7 @@ MESSAGES = [ bytes(b'\x56' * 32), bytes(b'\xab' * 32), ] +SAMPLE_MESSAGE = b'\x12' * 32 PRIVKEYS = [ # Curve order is 256 so private keys are 32 bytes at most. @@ -48,16 +49,30 @@ PRIVKEYS = [ hex_to_int('0x0000000000000000000000000000000047b8192d77bf871b62e87859d653922725724a5c031afeabc60bcef5ff665138'), hex_to_int('0x00000000000000000000000000000000328388aff0d4a5b7dc9205abd374e7e98f3cd9f3418edb4eafda5fb16473d216'), ] +PUBKEYS = [bls.SkToPk(privkey) for privkey in PRIVKEYS] Z1_PUBKEY = b'\xc0' + b'\x00' * 47 NO_SIGNATURE = b'\x00' * 96 Z2_SIGNATURE = b'\xc0' + b'\x00' * 95 +ZERO_PRIVKEY = 0 +ZERO_PRIVKEY_BYTES = b'\x00' * 32 + + +def expect_exception(func, *args): + try: + func(*args) + except Exception: + pass + else: + raise Exception("should have raised exception") def case01_sign(): + # Valid cases for privkey in PRIVKEYS: for message in MESSAGES: sig = bls.Sign(privkey, message) + assert sig == milagro_bls.Sign(to_bytes(privkey), message) # double-check with milagro identifier = f'{int_to_hex(privkey)}_{encode_hex(message)}' yield f'sign_case_{(hash(bytes(identifier, "utf-8"))[:8]).hex()}', { 'input': { @@ -66,6 +81,16 @@ def case01_sign(): }, 'output': encode_hex(sig) } + # Edge case: privkey == 0 + expect_exception(bls.Sign, ZERO_PRIVKEY, message) + # expect_exception(milagro_bls.Sign, ZERO_PRIVKEY_BYTES, message) # TODO: enable it when milagro is ready + yield f'sign_case_zero_privkey', { + 'input': { + 'privkey': ZERO_PRIVKEY_BYTES, + 'message': encode_hex(message), + }, + 'output': None + } def case02_verify(): @@ -120,17 +145,17 @@ def case02_verify(): 'output': False, } - # Valid pubkey and signature with the point at infinity - assert bls.Verify(Z1_PUBKEY, message, Z2_SIGNATURE) - assert milagro_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, - } + # Invalid pubkey and signature with the point at infinity + assert not bls.Verify(Z1_PUBKEY, SAMPLE_MESSAGE, Z2_SIGNATURE) + # assert not milagro_bls.Verify(Z1_PUBKEY, SAMPLE_MESSAGE, Z2_SIGNATURE) # TODO: enable it when milagro is ready + yield f'verify_infinity_pubkey_and_infinity_signature', { + 'input': { + 'pubkey': encode_hex(Z1_PUBKEY), + 'message': encode_hex(SAMPLE_MESSAGE), + 'signature': encode_hex(Z2_SIGNATURE), + }, + 'output': False, + } def case03_aggregate(): @@ -142,13 +167,7 @@ def case03_aggregate(): } # Invalid pubkeys -- len(pubkeys) == 0 - try: - bls.Aggregate([]) - except Exception: - pass - else: - raise Exception("Should have been INVALID") - + expect_exception(bls.Aggregate, []) # No signatures to aggregate. Follow IETF BLS spec, return `None` to represent INVALID. # https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-02#section-2.8 yield f'aggregate_na_signatures', { @@ -231,6 +250,23 @@ def case04_fast_aggregate_verify(): '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 bls.FastAggregateVerify(pubkeys_with_infinity, SAMPLE_MESSAGE, aggregate_signature) + # TODO: enable it when milagro is ready + # assert not milagro_bls.FastAggregateVerify(pubkeys_with_infinity, SAMPLE_MESSAGE, aggregate_signature) + yield f'fast_aggregate_verify_infinity_pubkey', { + 'input': { + 'pubkeys': [encode_hex(pubkey) for pubkey in pubkeys_with_infinity], + 'messages': encode_hex(SAMPLE_MESSAGE), + 'signature': encode_hex(aggregate_signature), + }, + 'output': False, + } + def case05_aggregate_verify(): pubkeys = [] @@ -295,6 +331,21 @@ def case05_aggregate_verify(): 'output': False, } + # Invalid pubkeys and signature -- pubkeys contains point at infinity + pubkeys_with_infinity = pubkeys + [Z1_PUBKEY] + messages_with_sample = messages + [SAMPLE_MESSAGE] + assert not bls.AggregateVerify(pubkeys_with_infinity, messages_with_sample, aggregate_signature) + # TODO: enable it when milagro is ready + # assert not milagro_bls.AggregateVerify(pubkeys_with_infinity, messages_with_sample, aggregate_signature) + yield f'aggregate_verify_infinity_pubkey', { + 'input': { + 'pubkeys': [encode_hex(pubkey) for pubkey in pubkeys_with_infinity], + 'messages': [encode_hex(message) for message in messages_with_sample], + 'signature': encode_hex(aggregate_signature), + }, + 'output': False, + } + def create_provider(handler_name: str, test_case_fn: Callable[[], Iterable[Tuple[str, Dict[str, Any]]]]) -> gen_typing.TestProvider: From bdbd2aae375d37da1f51a10fe460df154ea34e64 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Fri, 25 Sep 2020 14:40:42 +0800 Subject: [PATCH 2/6] Aggregate G2 point at infinity --- tests/generators/bls/main.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/tests/generators/bls/main.py b/tests/generators/bls/main.py index 93121f2a0..02ba84040 100644 --- a/tests/generators/bls/main.py +++ b/tests/generators/bls/main.py @@ -161,9 +161,11 @@ def case02_verify(): def case03_aggregate(): for message in MESSAGES: sigs = [bls.Sign(privkey, message) for privkey in PRIVKEYS] + aggregate_sig = bls.Aggregate(sigs) + assert aggregate_sig == milagro_bls.Aggregate(sigs) yield f'aggregate_{encode_hex(message)}', { 'input': [encode_hex(sig) for sig in sigs], - 'output': encode_hex(bls.Aggregate(sigs)), + 'output': encode_hex(aggregate_sig), } # Invalid pubkeys -- len(pubkeys) == 0 @@ -175,6 +177,14 @@ def case03_aggregate(): 'output': None, } + # Valid to aggregate G2 point at infinity + aggregate_sig = bls.Aggregate([Z2_SIGNATURE]) + assert aggregate_sig == milagro_bls.Aggregate([Z2_SIGNATURE]) == Z2_SIGNATURE + yield f'aggregate_infinity_signature', { + 'input': [Z2_SIGNATURE], + 'output': aggregate_sig, + } + def case04_fast_aggregate_verify(): for i, message in enumerate(MESSAGES): From ad4ad2d8b4ef70f1dcfb459ba0caf365daff0723 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Fri, 25 Sep 2020 14:50:01 +0800 Subject: [PATCH 3/6] Bump IETF BLS spec version draft 03 -> draft 04 --- specs/phase0/beacon-chain.md | 2 +- tests/generators/bls/main.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/specs/phase0/beacon-chain.md b/specs/phase0/beacon-chain.md index 69bc48d69..1ceb28d21 100644 --- a/specs/phase0/beacon-chain.md +++ b/specs/phase0/beacon-chain.md @@ -603,7 +603,7 @@ def bytes_to_uint64(data: bytes) -> uint64: #### BLS Signatures -Eth2 makes use of BLS signatures as specified in the [IETF draft BLS specification draft-irtf-cfrg-bls-signature-03](https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-03). Specifically, eth2 uses the `BLS_SIG_BLS12381G2_XMD:SHA-256_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-04](https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-04). 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` diff --git a/tests/generators/bls/main.py b/tests/generators/bls/main.py index 02ba84040..2260e2a8e 100644 --- a/tests/generators/bls/main.py +++ b/tests/generators/bls/main.py @@ -171,7 +171,7 @@ def case03_aggregate(): # Invalid pubkeys -- len(pubkeys) == 0 expect_exception(bls.Aggregate, []) # No signatures to aggregate. Follow IETF BLS spec, return `None` to represent INVALID. - # https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-02#section-2.8 + # https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-04#section-2.8 yield f'aggregate_na_signatures', { 'input': [], 'output': None, From b43f62de0e0fe50ccba6a82c79fe21b0bee36178 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Fri, 25 Sep 2020 15:05:10 +0800 Subject: [PATCH 4/6] Fix encoding --- tests/generators/bls/main.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/generators/bls/main.py b/tests/generators/bls/main.py index 2260e2a8e..32edc1c41 100644 --- a/tests/generators/bls/main.py +++ b/tests/generators/bls/main.py @@ -86,7 +86,7 @@ def case01_sign(): # expect_exception(milagro_bls.Sign, ZERO_PRIVKEY_BYTES, message) # TODO: enable it when milagro is ready yield f'sign_case_zero_privkey', { 'input': { - 'privkey': ZERO_PRIVKEY_BYTES, + 'privkey': encode_hex(ZERO_PRIVKEY_BYTES), 'message': encode_hex(message), }, 'output': None @@ -181,8 +181,8 @@ def case03_aggregate(): aggregate_sig = bls.Aggregate([Z2_SIGNATURE]) assert aggregate_sig == milagro_bls.Aggregate([Z2_SIGNATURE]) == Z2_SIGNATURE yield f'aggregate_infinity_signature', { - 'input': [Z2_SIGNATURE], - 'output': aggregate_sig, + 'input': [encode_hex(Z2_SIGNATURE)], + 'output': encode_hex(aggregate_sig), } From 4613c6b3338708d60526943ec971799d1994d41e Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Thu, 1 Oct 2020 00:44:47 +0800 Subject: [PATCH 5/6] Bump py_ecc to 5.0.0 --- setup.py | 2 +- tests/generators/bls/requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index b024e8d7f..350f4aedd 100644 --- a/setup.py +++ b/setup.py @@ -536,7 +536,7 @@ setup( "eth-utils>=1.3.0,<2", "eth-typing>=2.1.0,<3.0.0", "pycryptodome==3.9.4", - "py_ecc==4.0.0", + "py_ecc==5.0.0", "milagro_bls_binding==1.3.0", "dataclasses==0.6", "remerkleable==0.1.17", diff --git a/tests/generators/bls/requirements.txt b/tests/generators/bls/requirements.txt index 254705282..fd54b5b32 100644 --- a/tests/generators/bls/requirements.txt +++ b/tests/generators/bls/requirements.txt @@ -1,4 +1,4 @@ -py_ecc==4.0.0 +py_ecc==5.0.0 eth-utils==1.6.0 ../../core/gen_helpers ../../../ From 4d3ac72473b2ada262aeb415cbb595287649716e Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Mon, 5 Oct 2020 21:55:49 +0800 Subject: [PATCH 6/6] Bump milagro_bls_binding to `1.4.0`, handle the exception cases --- setup.py | 2 +- tests/core/pyspec/eth2spec/utils/bls.py | 9 +++++++++ tests/generators/bls/main.py | 10 +++++----- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/setup.py b/setup.py index 350f4aedd..451da1b73 100644 --- a/setup.py +++ b/setup.py @@ -537,7 +537,7 @@ setup( "eth-typing>=2.1.0,<3.0.0", "pycryptodome==3.9.4", "py_ecc==5.0.0", - "milagro_bls_binding==1.3.0", + "milagro_bls_binding==1.4.0", "dataclasses==0.6", "remerkleable==0.1.17", "ruamel.yaml==0.16.5", diff --git a/tests/core/pyspec/eth2spec/utils/bls.py b/tests/core/pyspec/eth2spec/utils/bls.py index dd3172b4b..88edbac61 100644 --- a/tests/core/pyspec/eth2spec/utils/bls.py +++ b/tests/core/pyspec/eth2spec/utils/bls.py @@ -10,6 +10,7 @@ bls = py_ecc_bls STUB_SIGNATURE = b'\x11' * 96 STUB_PUBKEY = b'\x22' * 48 +Z1_PUBKEY = b'\xc0' + b'\x00' * 47 Z2_SIGNATURE = b'\xc0' + b'\x00' * 95 STUB_COORDINATES = _signature_to_G2(Z2_SIGNATURE) @@ -66,6 +67,11 @@ def AggregateVerify(pubkeys, messages, signature): @only_with_bls(alt_return=True) def FastAggregateVerify(pubkeys, message, signature): + # TODO: remove it when milagro_bls_binding is fixed + # https://github.com/ChihChengLiang/milagro_bls_binding/issues/19 + if Z1_PUBKEY in pubkeys: + return False + try: result = bls.FastAggregateVerify(list(pubkeys), message, signature) except Exception: @@ -81,6 +87,9 @@ def Aggregate(signatures): @only_with_bls(alt_return=STUB_SIGNATURE) def Sign(SK, message): + # TODO: remove it when https://github.com/sigp/milagro_bls/issues/39 is fixed + if SK == 0: + raise Exception("SK should not be zero") if bls == py_ecc_bls: return bls.Sign(SK, message) else: diff --git a/tests/generators/bls/main.py b/tests/generators/bls/main.py index 32edc1c41..8cdc2a94b 100644 --- a/tests/generators/bls/main.py +++ b/tests/generators/bls/main.py @@ -83,7 +83,8 @@ def case01_sign(): } # Edge case: privkey == 0 expect_exception(bls.Sign, ZERO_PRIVKEY, message) - # expect_exception(milagro_bls.Sign, ZERO_PRIVKEY_BYTES, message) # TODO: enable it when milagro is ready + # TODO enable it when milagro_bls is ready for IETF BLS draft 04 + # expect_exception(milagro_bls.Sign, ZERO_PRIVKEY_BYTES, message) yield f'sign_case_zero_privkey', { 'input': { 'privkey': encode_hex(ZERO_PRIVKEY_BYTES), @@ -147,7 +148,7 @@ def case02_verify(): # Invalid pubkey and signature with the point at infinity assert not bls.Verify(Z1_PUBKEY, SAMPLE_MESSAGE, Z2_SIGNATURE) - # assert not milagro_bls.Verify(Z1_PUBKEY, SAMPLE_MESSAGE, Z2_SIGNATURE) # TODO: enable it when milagro is ready + assert not milagro_bls.Verify(Z1_PUBKEY, SAMPLE_MESSAGE, Z2_SIGNATURE) yield f'verify_infinity_pubkey_and_infinity_signature', { 'input': { 'pubkey': encode_hex(Z1_PUBKEY), @@ -266,7 +267,7 @@ def case04_fast_aggregate_verify(): signatures = [bls.Sign(privkey, SAMPLE_MESSAGE) for privkey in PRIVKEYS] aggregate_signature = bls.Aggregate(signatures) assert not bls.FastAggregateVerify(pubkeys_with_infinity, SAMPLE_MESSAGE, aggregate_signature) - # TODO: enable it when milagro is ready + # TODO enable it when milagro_bls is ready for IETF BLS draft 04 # assert not milagro_bls.FastAggregateVerify(pubkeys_with_infinity, SAMPLE_MESSAGE, aggregate_signature) yield f'fast_aggregate_verify_infinity_pubkey', { 'input': { @@ -345,8 +346,7 @@ def case05_aggregate_verify(): pubkeys_with_infinity = pubkeys + [Z1_PUBKEY] messages_with_sample = messages + [SAMPLE_MESSAGE] assert not bls.AggregateVerify(pubkeys_with_infinity, messages_with_sample, aggregate_signature) - # TODO: enable it when milagro is ready - # assert not milagro_bls.AggregateVerify(pubkeys_with_infinity, messages_with_sample, aggregate_signature) + assert not milagro_bls.AggregateVerify(pubkeys_with_infinity, messages_with_sample, aggregate_signature) yield f'aggregate_verify_infinity_pubkey', { 'input': { 'pubkeys': [encode_hex(pubkey) for pubkey in pubkeys_with_infinity],