diff --git a/Makefile b/Makefile index 371a3ecf8a..562318fa73 100644 --- a/Makefile +++ b/Makefile @@ -117,10 +117,10 @@ citest: pyspec mkdir -p $(TEST_REPORT_DIR); ifdef fork . venv/bin/activate; cd $(PY_SPEC_DIR); \ - python3 -m pytest -n 16 --bls-type=milagro --preset=$(TEST_PRESET_TYPE) --fork=$(fork) --junitxml=test-reports/test_results.xml eth2spec + python3 -m pytest -n 16 --bls-type=fastest --preset=$(TEST_PRESET_TYPE) --fork=$(fork) --junitxml=test-reports/test_results.xml eth2spec else . venv/bin/activate; cd $(PY_SPEC_DIR); \ - python3 -m pytest -n 16 --bls-type=milagro --preset=$(TEST_PRESET_TYPE) --junitxml=test-reports/test_results.xml eth2spec + python3 -m pytest -n 16 --bls-type=fastest --preset=$(TEST_PRESET_TYPE) --junitxml=test-reports/test_results.xml eth2spec endif diff --git a/setup.py b/setup.py index 9c5488f126..cf030c5492 100644 --- a/setup.py +++ b/setup.py @@ -1174,5 +1174,6 @@ def run(self): RUAMEL_YAML_VERSION, "lru-dict==1.1.8", MARKO_VERSION, + "py_arkworks_bls12381==0.3.4", ] ) diff --git a/specs/deneb/polynomial-commitments.md b/specs/deneb/polynomial-commitments.md index 61e22e1820..6371b12cb8 100644 --- a/specs/deneb/polynomial-commitments.md +++ b/specs/deneb/polynomial-commitments.md @@ -273,7 +273,7 @@ def g1_lincomb(points: Sequence[KZGCommitment], scalars: Sequence[BLSFieldElemen BLS multiscalar multiplication. This function can be optimized using Pippenger's algorithm and variants. """ assert len(points) == len(scalars) - result = bls.Z1 + result = bls.Z1() for x, a in zip(points, scalars): result = bls.add(result, bls.multiply(bls.bytes48_to_G1(x), a)) return KZGCommitment(bls.G1_to_bytes48(result)) @@ -371,10 +371,10 @@ def verify_kzg_proof_impl(commitment: KZGCommitment, Verify KZG proof that ``p(z) == y`` where ``p(z)`` is the polynomial represented by ``polynomial_kzg``. """ # Verify: P - y = Q * (X - z) - X_minus_z = bls.add(bls.bytes96_to_G2(KZG_SETUP_G2[1]), bls.multiply(bls.G2, BLS_MODULUS - z)) - P_minus_y = bls.add(bls.bytes48_to_G1(commitment), bls.multiply(bls.G1, BLS_MODULUS - y)) + X_minus_z = bls.add(bls.bytes96_to_G2(KZG_SETUP_G2[1]), bls.multiply(bls.G2(), BLS_MODULUS - z)) + P_minus_y = bls.add(bls.bytes48_to_G1(commitment), bls.multiply(bls.G1(), BLS_MODULUS - y)) return bls.pairing_check([ - [P_minus_y, bls.neg(bls.G2)], + [P_minus_y, bls.neg(bls.G2())], [bls.bytes48_to_G1(proof), X_minus_z] ]) ``` @@ -415,14 +415,14 @@ def verify_kzg_proof_batch(commitments: Sequence[KZGCommitment], proofs, [BLSFieldElement((int(z) * int(r_power)) % BLS_MODULUS) for z, r_power in zip(zs, r_powers)], ) - C_minus_ys = [bls.add(bls.bytes48_to_G1(commitment), bls.multiply(bls.G1, BLS_MODULUS - y)) + C_minus_ys = [bls.add(bls.bytes48_to_G1(commitment), bls.multiply(bls.G1(), BLS_MODULUS - y)) for commitment, y in zip(commitments, ys)] C_minus_y_as_KZGCommitments = [KZGCommitment(bls.G1_to_bytes48(x)) for x in C_minus_ys] C_minus_y_lincomb = g1_lincomb(C_minus_y_as_KZGCommitments, r_powers) return bls.pairing_check([ [bls.bytes48_to_G1(proof_lincomb), bls.neg(bls.bytes96_to_G2(KZG_SETUP_G2[1]))], - [bls.add(bls.bytes48_to_G1(C_minus_y_lincomb), bls.bytes48_to_G1(proof_z_lincomb)), bls.G2] + [bls.add(bls.bytes48_to_G1(C_minus_y_lincomb), bls.bytes48_to_G1(proof_z_lincomb)), bls.G2()] ]) ``` @@ -561,3 +561,4 @@ def verify_blob_kzg_proof_batch(blobs: Sequence[Blob], return verify_kzg_proof_batch(commitments, evaluation_challenges, ys, proofs) ``` + diff --git a/tests/core/pyspec/eth2spec/test/conftest.py b/tests/core/pyspec/eth2spec/test/conftest.py index a5f19e20cb..afe40f7701 100644 --- a/tests/core/pyspec/eth2spec/test/conftest.py +++ b/tests/core/pyspec/eth2spec/test/conftest.py @@ -44,8 +44,11 @@ def pytest_addoption(parser): help="bls-default: make tests that are not dependent on BLS run without BLS" ) parser.addoption( - "--bls-type", action="store", type=str, default="py_ecc", choices=["py_ecc", "milagro"], - help="bls-type: use 'pyecc' or 'milagro' implementation for BLS" + "--bls-type", action="store", type=str, default="py_ecc", choices=["py_ecc", "milagro", "arkworks", "fastest"], + help=( + "bls-type: use specified BLS implementation;" + "fastest' uses milagro for signatures and arkworks for everything else (e.g. KZG)" + ) ) @@ -88,5 +91,9 @@ def bls_type(request): bls_utils.use_py_ecc() elif bls_type == "milagro": bls_utils.use_milagro() + elif bls_type == "arkworks": + bls_utils.use_arkworks() + elif bls_type == "fastest": + bls_utils.use_fastest() else: raise Exception(f"unrecognized bls type: {bls_type}") diff --git a/tests/core/pyspec/eth2spec/utils/bls.py b/tests/core/pyspec/eth2spec/utils/bls.py index aa060f4f9a..d9e6bffd26 100644 --- a/tests/core/pyspec/eth2spec/utils/bls.py +++ b/tests/core/pyspec/eth2spec/utils/bls.py @@ -1,28 +1,49 @@ from py_ecc.bls import G2ProofOfPossession as py_ecc_bls from py_ecc.bls.g2_primatives import signature_to_G2 as _signature_to_G2 from py_ecc.optimized_bls12_381 import ( # noqa: F401 - G1, - G2, - Z1, - Z2, - FQ, - add, - multiply, - neg, - pairing, - final_exponentiate, - FQ12 + G1 as py_ecc_G1, + G2 as py_ecc_G2, + Z1 as py_ecc_Z1, + add as py_ecc_add, + multiply as py_ecc_mul, + neg as py_ecc_neg, + pairing as py_ecc_pairing, + final_exponentiate as py_ecc_final_exponentiate, + FQ12 as py_ecc_GT ) from py_ecc.bls.g2_primitives import ( # noqa: F401 - G1_to_pubkey as G1_to_bytes48, - pubkey_to_G1 as bytes48_to_G1, - G2_to_signature as G2_to_bytes96, - signature_to_G2 as bytes96_to_G2, + G1_to_pubkey as py_ecc_G1_to_bytes48, + pubkey_to_G1 as py_ecc_bytes48_to_G1, + G2_to_signature as py_ecc_G2_to_bytes96, + signature_to_G2 as py_ecc_bytes96_to_G2, +) +from py_arkworks_bls12381 import ( + G1Point as arkworks_G1, + G2Point as arkworks_G2, + Scalar as arkworks_Scalar, + GT as arkworks_GT ) import milagro_bls_binding as milagro_bls # noqa: F401 for BLS switching option +import py_arkworks_bls12381 as arkworks_bls # noqa: F401 for BLS switching option + + +class fastest_bls: + G1 = arkworks_G1 + G2 = arkworks_G2 + Scalar = arkworks_Scalar + GT = arkworks_GT + _AggregatePKs = milagro_bls._AggregatePKs + Sign = milagro_bls.Sign + Verify = milagro_bls.Verify + Aggregate = milagro_bls.Aggregate + AggregateVerify = milagro_bls.AggregateVerify + FastAggregateVerify = milagro_bls.FastAggregateVerify + SkToPk = milagro_bls.SkToPk + + # Flag to make BLS active or not. Used for testing, do not ignore BLS in production unless you know what you are doing. bls_active = True @@ -43,6 +64,14 @@ def use_milagro(): bls = milagro_bls +def use_arkworks(): + """ + Shortcut to use Arkworks as BLS library + """ + global bls + bls = arkworks_bls + + def use_py_ecc(): """ Shortcut to use Py-ecc as BLS library @@ -51,6 +80,14 @@ def use_py_ecc(): bls = py_ecc_bls +def use_fastest(): + """ + Shortcut to use Milagro for signatures and Arkworks for other BLS operations + """ + global bls + bls = fastest_bls + + def only_with_bls(alt_return=None): """ Decorator factory to make a function only run when BLS is active. Otherwise return the default. @@ -68,7 +105,10 @@ def entry(*args, **kw): @only_with_bls(alt_return=True) def Verify(PK, message, signature): try: - result = bls.Verify(PK, message, signature) + if bls == arkworks_bls: # no signature API in arkworks + result = py_ecc_bls.Verify(PK, message, signature) + else: + result = bls.Verify(PK, message, signature) except Exception: result = False finally: @@ -78,7 +118,10 @@ def Verify(PK, message, signature): @only_with_bls(alt_return=True) def AggregateVerify(pubkeys, messages, signature): try: - result = bls.AggregateVerify(list(pubkeys), list(messages), signature) + if bls == arkworks_bls: # no signature API in arkworks + result = py_ecc_bls.AggregateVerify(list(pubkeys), list(messages), signature) + else: + result = bls.AggregateVerify(list(pubkeys), list(messages), signature) except Exception: result = False finally: @@ -88,7 +131,10 @@ def AggregateVerify(pubkeys, messages, signature): @only_with_bls(alt_return=True) def FastAggregateVerify(pubkeys, message, signature): try: - result = bls.FastAggregateVerify(list(pubkeys), message, signature) + if bls == arkworks_bls: # no signature API in arkworks + result = py_ecc_bls.FastAggregateVerify(list(pubkeys), message, signature) + else: + result = bls.FastAggregateVerify(list(pubkeys), message, signature) except Exception: result = False finally: @@ -97,12 +143,16 @@ def FastAggregateVerify(pubkeys, message, signature): @only_with_bls(alt_return=STUB_SIGNATURE) def Aggregate(signatures): + if bls == arkworks_bls: # no signature API in arkworks + return py_ecc_bls.Aggregate(signatures) return bls.Aggregate(signatures) @only_with_bls(alt_return=STUB_SIGNATURE) def Sign(SK, message): - if bls == py_ecc_bls: + if bls == arkworks_bls: # no signature API in arkworks + return py_ecc_bls.Sign(SK, message) + elif bls == py_ecc_bls: return bls.Sign(SK, message) else: return bls.Sign(SK.to_bytes(32, 'big'), message) @@ -121,24 +171,143 @@ def AggregatePKs(pubkeys): # milagro_bls._AggregatePKs checks KeyValidate internally pass + if bls == arkworks_bls: # no signature API in arkworks + return milagro_bls._AggregatePKs(list(pubkeys)) + return bls._AggregatePKs(list(pubkeys)) @only_with_bls(alt_return=STUB_SIGNATURE) def SkToPk(SK): - if bls == py_ecc_bls: - return bls.SkToPk(SK) + if bls == py_ecc_bls or bls == arkworks_bls: # no signature API in arkworks + return py_ecc_bls.SkToPk(SK) else: return bls.SkToPk(SK.to_bytes(32, 'big')) def pairing_check(values): - p_q_1, p_q_2 = values - final_exponentiation = final_exponentiate( - pairing(p_q_1[1], p_q_1[0], final_exponentiate=False) - * pairing(p_q_2[1], p_q_2[0], final_exponentiate=False) - ) - return final_exponentiation == FQ12.one() + if bls == arkworks_bls or bls == fastest_bls: + p_q_1, p_q_2 = values + g1s = [p_q_1[0], p_q_2[0]] + g2s = [p_q_1[1], p_q_2[1]] + return arkworks_GT.multi_pairing(g1s, g2s) == arkworks_GT.one() + else: + p_q_1, p_q_2 = values + final_exponentiation = py_ecc_final_exponentiate( + py_ecc_pairing(p_q_1[1], p_q_1[0], final_exponentiate=False) + * py_ecc_pairing(p_q_2[1], p_q_2[0], final_exponentiate=False) + ) + return final_exponentiation == py_ecc_GT.one() + + +def add(lhs, rhs): + """ + Performs point addition of `lhs` and `rhs`. + The points can either be in G1 or G2. + """ + if bls == arkworks_bls or bls == fastest_bls: + return lhs + rhs + return py_ecc_add(lhs, rhs) + + +def multiply(point, scalar): + """ + Performs Scalar multiplication between + `point` and `scalar`. + `point` can either be in G1 or G2 + """ + if bls == arkworks_bls or bls == fastest_bls: + int_as_bytes = scalar.to_bytes(32, 'little') + scalar = arkworks_Scalar.from_le_bytes(int_as_bytes) + return point * scalar + return py_ecc_mul(point, scalar) + + +def neg(point): + """ + Returns the point negation of `point` + `point` can either be in G1 or G2 + """ + if bls == arkworks_bls or bls == fastest_bls: + return -point + return py_ecc_neg(point) + + +def Z1(): + """ + Returns the identity point in G1 + """ + if bls == arkworks_bls or bls == fastest_bls: + return arkworks_G1.identity() + return py_ecc_Z1 + + +def G1(): + """ + Returns the chosen generator point in G1 + """ + if bls == arkworks_bls or bls == fastest_bls: + return arkworks_G1() + return py_ecc_G1 + + +def G2(): + """ + Returns the chosen generator point in G2 + """ + if bls == arkworks_bls or bls == fastest_bls: + return arkworks_G2() + return py_ecc_G2 + + +def G1_to_bytes48(point): + """ + Serializes a point in G1. + Returns a bytearray of size 48 as + we use the compressed format + """ + if bls == arkworks_bls or bls == fastest_bls: + return point.to_compressed_bytes() + return py_ecc_G1_to_bytes48(point) + + +def G2_to_bytes96(point): + """ + Serializes a point in G2. + Returns a bytearray of size 96 as + we use the compressed format + """ + if bls == arkworks_bls or bls == fastest_bls: + return point.to_compressed_bytes() + return py_ecc_G2_to_bytes96(point) + + +def bytes48_to_G1(bytes48): + """ + Deserializes a purported compressed serialized + point in G1. + - No subgroup checks are performed + - If the bytearray is not a valid serialization + of a point in G1, then this method will raise + an exception + """ + if bls == arkworks_bls or bls == fastest_bls: + return arkworks_G1.from_compressed_bytes_unchecked(bytes48) + return py_ecc_bytes48_to_G1(bytes48) + + +def bytes96_to_G2(bytes96): + """ + Deserializes a purported compressed serialized + point in G2. + - No subgroup checks are performed + - If the bytearray is not a valid serialization + of a point in G2, then this method will raise + an exception + """ + if bls == arkworks_bls or bls == fastest_bls: + return arkworks_G2.from_compressed_bytes_unchecked(bytes96) + return py_ecc_bytes96_to_G2(bytes96) @only_with_bls(alt_return=True)