Skip to content

Commit

Permalink
Add implementations of some BLS precompiles
Browse files Browse the repository at this point in the history
  • Loading branch information
ralexstokes committed Jun 2, 2020
1 parent b069648 commit d0ad61f
Show file tree
Hide file tree
Showing 4 changed files with 274 additions and 1 deletion.
9 changes: 9 additions & 0 deletions eth/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,15 @@
GAS_ECPAIRING_BASE = 100000
GAS_ECPAIRING_PER_POINT = 80000

GAS_BLS_G1_ADD = 600
GAS_BLS_G1_MUL = 12000
GAS_BLS_G2_ADD = 4500
GAS_BLS_G2_MUL = 55000
GAS_BLS_PAIRING_BASE = 115000
GAS_BLS_PAIRING_PER_PAIR = 115000
GAS_BLS_MAP_FP_TO_G1 = 5500
GAS_BLS_MAP_FP2_TO_G2 = 110000


#
# Gas Limit
Expand Down
11 changes: 11 additions & 0 deletions eth/precompiles/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,14 @@
from .ecmul import ecmul # noqa: F401
from .ecpairing import ecpairing # noqa: F401
from .blake2 import blake2b_fcompress # noqa: F401
from .bls import ( # noqa: F401
g1_add as bls_g1_add,
g1_mul as bls_g1_mul,
g1_multiexp as bls_g1_multiexp,
g2_add as bls_g2_add,
g2_mul as bls_g2_mul,
g2_multiexp as bls_g2_multiexp,
pairing as bls_pairing,
map_fp_to_g1 as bls_map_fp_to_g1,
map_fp2_to_g2 as bls_map_fp2_to_g2,
)
232 changes: 232 additions & 0 deletions eth/precompiles/bls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
from typing import Tuple

from eth_utils import (
ValidationError,
big_endian_to_int,
)
from py_ecc import (
optimized_bls12_381 as bls12_381,
bls
)

from eth import constants
from eth.exceptions import (
VMError,
)

from eth.vm.computation import (
BaseComputation,
)


FP2_SIZE_IN_BYTES = 128
G1_SIZE_IN_BYTES = 128
G2_SIZE_IN_BYTES = 256

G1Point = Tuple[bls12_381.FQ, bls12_381.FQ]
G2Point = Tuple[bls12_381.FQ2, bls12_381.FQ2]


def _parse_g1_point(data: bytes) -> G1Point:
if len(data) != G1_SIZE_IN_BYTES:
raise ValidationError("invalid size of G1 input")

point = (
bls12_381.FQ(
int.from_bytes(data[0:64], byteorder="big")
),
bls12_381.FQ(
int.from_bytes(data[64:128], byteorder="big")
)
)

if not bls12_381.is_on_curve(point, bls12_381.b):
raise ValidationError("invalid G1 point not on curve")

return point


def g1_add(computation: BaseComputation,
gas_cost: int = constants.GAS_BLS_G1_ADD) -> BaseComputation:
raise NotImplementedError()


def g1_mul(computation: BaseComputation,
gas_cost: int = constants.GAS_BLS_G1_MUL) -> BaseComputation:
raise NotImplementedError()


def g1_multiexp(computation: BaseComputation) -> BaseComputation:
# NOTE: gas cost involves a discount based on the number of points involved
# TODO load discount table and compute gas cost based on number of inputs
raise NotImplementedError()


def _parse_g2_point(data: bytes) -> G2Point:
if len(data) != G2_SIZE_IN_BYTES:
raise ValidationError("invalid size of G2 input")

point = (
bls12_381.FQ2(
(
int.from_bytes(data[0:64], byteorder="big"),
int.from_bytes(data[64:128], byteorder="big")
)
),
bls12_381.FQ2(
(
int.from_bytes(data[128:192], byteorder="big"),
int.from_bytes(data[192:256], byteorder="big")
)
)
)

if not bls12_381.is_on_curve(point, bls12_381.b2):
raise ValidationError("invalid G2 point not on curve")

return point


def _serialize_g2(result: G2Point) -> bytes:
return b"".join(
(
result[0][0].to_bytes(64, byteorder="big"),
result[0][1].to_bytes(64, byteorder="big"),
result[1][0].to_bytes(64, byteorder="big"),
result[1][1].to_bytes(64, byteorder="big"),
)
)


def _g2_add(x: G2Point, y: G2Point) -> G2Point:
result = bls12_381.add((x[0], x[1], bls12_381.FQ2.one()), (y[0], y[1], bls12_381.FQ2.one()))
return bls12_381.normalize(result)


def g2_add(computation: BaseComputation,
gas_cost: int = constants.GAS_BLS_G2_ADD) -> BaseComputation:
computation.consume_gas(gas_cost, reason='BLS_G2_ADD Precompile')

try:
input_data = computation.msg.data_as_bytes
x = _parse_g2_point(input_data[:G2_SIZE_IN_BYTES])
y = _parse_g2_point(input_data[G2_SIZE_IN_BYTES:])
result = _g2_add(x, y)
except ValidationError:
raise VMError("Invalid BLS_G2_ADD parameters")

computation.output = _serialize_g2(result)
return computation


def _g2_mul(x: G2Point, k: int) -> G2Point:
result = bls12_381.multiply((x[0], x[1], bls12_381.FQ2.one()), k)
return bls12_381.normalize(result)


def _parse_scalar(data: bytes) -> int:
if len(data) != 32:
raise ValidationError("invalid size of scalar input")

return big_endian_to_int(data)


def g2_mul(computation: BaseComputation,
gas_cost: int = constants.GAS_BLS_G2_MUL) -> BaseComputation:
computation.consume_gas(gas_cost, reason='BLS_G2_MUL Precompile')

try:
input_data = computation.msg.data_as_bytes
x = _parse_g2_point(input_data[:G2_SIZE_IN_BYTES])
k = _parse_scalar(input_data[G2_SIZE_IN_BYTES:])
result = _g2_mul(x, k)
except ValidationError:
raise VMError("Invalid BLS_G2_MUL parameters")

computation.output = _serialize_g2(result)
return computation


def g2_multiexp(computation: BaseComputation) -> BaseComputation:
# NOTE: gas cost involves a discount based on the number of points involved
# TODO load discount table and compute gas cost based on number of inputs
raise NotImplementedError()


def _pairing(input_data: bytes) -> bool:
field_element = bls12_381.FQ12.one()
g1_to_g2_offset = G1_SIZE_IN_BYTES + G2_SIZE_IN_BYTES
for next_index in range(0, len(input_data), 384):
p = _parse_g1_point(input_data[next_index:next_index + G1_SIZE_IN_BYTES])

q = _parse_g2_point(
input_data[next_index + G1_SIZE_IN_BYTES:next_index + g1_to_g2_offset]
)
projective_p = (p[0], p[1], bls12_381.FQ.one())
projective_q = (q[0], q[1], bls12_381.FQ2.one())
field_element *= bls12_381.pairing(projective_q, projective_p, final_exponentiate=False)

return bls12_381.final_exponentiate(field_element) == bls12_381.FQ12.one()


def _serialize_boolean(value: bool) -> bytes:
return int(value).to_bytes(32, byteorder="big")


def pairing(computation: BaseComputation,
gas_cost_base: int = constants.GAS_BLS_PAIRING_BASE,
gas_cost_per_pair: int = constants.GAS_BLS_PAIRING_PER_PAIR) -> BaseComputation:
input_data = computation.msg.data_as_bytes
if len(input_data) % 384:
# data length must be an exact multiple of 384
raise VMError("Invalid BLS_PAIRING parameters")

num_points = len(input_data) // 384
gas_cost = gas_cost_base + num_points * gas_cost_per_pair

computation.consume_gas(gas_cost, reason='BLS_PAIRING Precompile')

try:
result = _pairing(input_data)
except ValidationError:
raise VMError("Invalid BLS_PAIRING parameters")

computation.output = _serialize_boolean(result)
return computation


def map_fp_to_g1(computation: BaseComputation,
gas_cost: int = constants.GAS_BLS_MAP_FP_TO_G1) -> BaseComputation:
raise NotImplementedError()


def _parse_fp2_element(data: bytes) -> bls12_381.FQ2:
if len(data) != FP2_SIZE_IN_BYTES:
raise ValidationError("invalid size of FP2 input")

return bls12_381.FQ2(
(
int.from_bytes(data[:64], byteorder="big"),
int.from_bytes(data[64:], byteorder="big")
)
)


def _map_fp2_to_g2(x: bls12_381.FQ2) -> G2Point:
point = bls.hash_to_curve.map_to_curve_G2(x)
return bls12_381.normalize(point)


def map_fp2_to_g2(computation: BaseComputation,
gas_cost: int = constants.GAS_BLS_MAP_FP2_TO_G2) -> BaseComputation:
computation.consume_gas(gas_cost, reason='BLS_MAP_FP2_TO_G2 Precompile')

try:
input_data = computation.msg.data_as_bytes
x = _parse_fp2_element(input_data[:FP2_SIZE_IN_BYTES])
result = _map_fp2_to_g2(x)
except ValidationError:
raise VMError("Invalid BLS_MAP_FP2_TO_G2 parameters")

computation.output = _serialize_g2(result)
return computation
23 changes: 22 additions & 1 deletion eth/vm/forks/berlin/computation.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
from eth_utils.toolz import (
merge,
)

from eth import precompiles
from eth._utils.address import (
force_bytes_to_address,
)
from eth.vm.forks.muir_glacier.computation import (
MUIR_GLACIER_PRECOMPILES
)
Expand All @@ -7,7 +15,20 @@

from .opcodes import BERLIN_OPCODES

BERLIN_PRECOMPILES = MUIR_GLACIER_PRECOMPILES
BERLIN_PRECOMPILES = merge(
MUIR_GLACIER_PRECOMPILES,
{
force_bytes_to_address(b'\x0a'): precompiles.bls_g1_add,
force_bytes_to_address(b'\x0b'): precompiles.bls_g1_mul,
force_bytes_to_address(b'\x0c'): precompiles.bls_g1_multiexp,
force_bytes_to_address(b'\x0d'): precompiles.bls_g2_add,
force_bytes_to_address(b'\x0e'): precompiles.bls_g2_mul,
force_bytes_to_address(b'\x0f'): precompiles.bls_g2_multiexp,
force_bytes_to_address(b'\x10'): precompiles.bls_pairing,
force_bytes_to_address(b'\x11'): precompiles.bls_map_fp_to_g1,
force_bytes_to_address(b'\x12'): precompiles.bls_map_fp2_to_g2,
}
)


class BerlinComputation(MuirGlacierComputation):
Expand Down

0 comments on commit d0ad61f

Please sign in to comment.