Skip to content
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

Bump IETF BLS spec version draft 03 -> draft 04 #2080

Merged
merged 6 commits into from
Oct 5, 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
4 changes: 2 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -536,8 +536,8 @@ def run(self):
"eth-utils>=1.3.0,<2",
"eth-typing>=2.1.0,<3.0.0",
"pycryptodome==3.9.4",
"py_ecc==4.0.0",
"milagro_bls_binding==1.3.0",
"py_ecc==5.0.0",
"milagro_bls_binding==1.4.0",
"dataclasses==0.6",
"remerkleable==0.1.17",
"ruamel.yaml==0.16.5",
Expand Down
2 changes: 1 addition & 1 deletion specs/phase0/beacon-chain.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down
14 changes: 13 additions & 1 deletion tests/core/pyspec/eth2spec/utils/bls.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

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)

Expand Down Expand Up @@ -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:
Expand All @@ -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:
Expand All @@ -99,4 +108,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'))
2 changes: 1 addition & 1 deletion tests/formats/bls/sign.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
101 changes: 81 additions & 20 deletions tests/generators/bls/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ def hex_to_int(x: str) -> int:
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.
Expand All @@ -48,16 +49,30 @@ def hex_to_int(x: str) -> int:
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': {
Expand All @@ -66,6 +81,17 @@ def case01_sign():
},
'output': encode_hex(sig)
}
# Edge case: privkey == 0
expect_exception(bls.Sign, ZERO_PRIVKEY, message)
# 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),
'message': encode_hex(message),
},
'output': None
}


def case02_verify():
Expand Down Expand Up @@ -120,42 +146,46 @@ 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)
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():
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
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
# https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-04#section-2.8
yield f'aggregate_na_signatures', {
'input': [],
'output': None,
}

# Valid to aggregate G2 point at infinity
hwwhww marked this conversation as resolved.
Show resolved Hide resolved
aggregate_sig = bls.Aggregate([Z2_SIGNATURE])
assert aggregate_sig == milagro_bls.Aggregate([Z2_SIGNATURE]) == Z2_SIGNATURE
yield f'aggregate_infinity_signature', {
'input': [encode_hex(Z2_SIGNATURE)],
'output': encode_hex(aggregate_sig),
}


def case04_fast_aggregate_verify():
for i, message in enumerate(MESSAGES):
Expand Down Expand Up @@ -231,6 +261,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_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': {
'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 = []
Expand Down Expand Up @@ -295,6 +342,20 @@ 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)
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:
Expand Down
2 changes: 1 addition & 1 deletion tests/generators/bls/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
py_ecc==4.0.0
py_ecc==5.0.0
eth-utils==1.6.0
../../core/gen_helpers
../../../