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

Implements All G2 ciphersuites from the BLS Standards #83

Merged
merged 17 commits into from
Jan 6, 2020
Merged
11 changes: 4 additions & 7 deletions py_ecc/bls/__init__.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
from .api import ( # noqa: F401
aggregate_pubkeys,
aggregate_signatures,
privtopub,
sign,
verify,
verify_multiple,
from .ciphersuites import ( # noqa: F401
G2Basic,
G2MessageAugmentation,
G2PoP,
)
119 changes: 0 additions & 119 deletions py_ecc/bls/api.py

This file was deleted.

187 changes: 187 additions & 0 deletions py_ecc/bls/ciphersuites.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
from typing import (
Iterable,
CarlBeek marked this conversation as resolved.
Show resolved Hide resolved
Tuple,
)
from math import (
ceil,
log2,
)
from abc import (
ABC,
abstractproperty,
)
from eth_typing import (
BLSPubkey,
BLSSignature,
)
from eth_utils import (
big_endian_to_int,
ValidationError,
)

from py_ecc.fields import optimized_bls12_381_FQ12 as FQ12
from py_ecc.optimized_bls12_381 import (
add,
curve_order,
final_exponentiate,
G1,
multiply,
neg,
pairing,
Z1,
Z2,
)

from .hash import (
hkdf_expand,
hkdf_extract,
)
from .hash_to_curve import hash_to_G2
from .g2_primatives import (
G1_to_pubkey,
G2_to_signature,
pubkey_to_G1,
signature_to_G2,
)


class BaseG2Ciphersuite(ABC):
@abstractproperty
def DST(self):
pass

@staticmethod
def PrivToPub(privkey: int) -> BLSPubkey:
return G1_to_pubkey(multiply(G1, privkey))

@staticmethod
def KeyGen(IKM: bytes) -> Tuple[BLSPubkey, int]:
prk = hkdf_extract(b'BLS-SIG-KEYGEN-SALT-', IKM)
l = ceil((1.5 * ceil(log2(curve_order))) / 8) # noqa: E741
okm = hkdf_expand(prk, b'', l)
x = big_endian_to_int(okm) % curve_order
return (BaseG2Ciphersuite.PrivToPub(x), x)

@staticmethod
def KeyValidate(PK: BLSPubkey) -> bool:
try:
pubkey_to_G1(PK)
except ValidationError:
return False
return True

@staticmethod
def _CoreSign(SK: int, message: bytes, DST: bytes) -> BLSSignature:
message_point = hash_to_G2(message, DST)
signature_point = multiply(message_point, SK)
return G2_to_signature(signature_point)

@staticmethod
def _CoreVerify(PK: BLSPubkey, message: bytes, signature: BLSSignature, DST: bytes) -> bool:
try:
signature_point = signature_to_G2(signature)
final_exponentiation = final_exponentiate(
pairing(
signature_point,
G1,
final_exponentiate=False,
) * pairing(
hash_to_G2(message, DST),
CarlBeek marked this conversation as resolved.
Show resolved Hide resolved
neg(pubkey_to_G1(PK)),
final_exponentiate=False,
)
)
return final_exponentiation == FQ12.one()
except (ValidationError, ValueError, AssertionError):
return False

@staticmethod
def Aggregate(signatures: Iterable[BLSSignature]) -> BLSSignature:
accumulator = Z2 # Seed with the point at infinity
for signature in signatures:
signature_point = signature_to_G2(signature)
accumulator = add(accumulator, signature_point)
return G2_to_signature(accumulator)

@staticmethod
def _CoreAggregateVerify(pairs: Iterable[Tuple[BLSPubkey, bytes]],
signature: BLSSignature, DST: bytes) -> bool:
try:
signature_point = signature_to_G2(signature)
accumulator = FQ12([1] + [0] * 11)
CarlBeek marked this conversation as resolved.
Show resolved Hide resolved
for pk, message in pairs:
pubkey_point = pubkey_to_G1(pk)
message_point = hash_to_G2(message, DST)
accumulator *= pairing(message_point, pubkey_point, final_exponentiate=False)
accumulator *= pairing(signature_point, neg(G1), final_exponentiate=False)
return final_exponentiate(accumulator) == FQ12.one()

except (ValidationError, ValueError, AssertionError):
return False

def Sign(self, SK: int, message: bytes) -> BLSSignature:
return self._CoreSign(SK, message, self.DST)
CarlBeek marked this conversation as resolved.
Show resolved Hide resolved

def Verify(self, PK: BLSPubkey, message: bytes, signature: BLSSignature) -> bool:
return self._CoreVerify(PK, message, signature, self.DST)

def AggregateVerify(self, pairs: Iterable[Tuple[BLSPubkey, bytes]],
signature: BLSSignature) -> bool:
pass
CarlBeek marked this conversation as resolved.
Show resolved Hide resolved
CarlBeek marked this conversation as resolved.
Show resolved Hide resolved


class G2Basic(BaseG2Ciphersuite):
DST = b'BLS_SIG_BLS12381G2-SHA256-SSWU-RO-_NUL_'

def AggregateVerify(self, pairs: Iterable[Tuple[BLSPubkey, bytes]],
signature: BLSSignature) -> bool:
pairs = list(pairs)
_, messages = zip(*pairs)
if len(messages) != len(set(messages)): # Messages are not unique
return False
return self._CoreAggregateVerify(pairs, signature, self.DST)


class G2MessageAugmentation(BaseG2Ciphersuite):
DST = b'BLS_SIG_BLS12381G2-SHA256-SSWU-RO-_AUG_'

def Sign(self, SK: int, message: bytes) -> BLSSignature:
PK = self.PrivToPub(SK)
return self._CoreSign(SK, PK + message, self.DST)

def Verify(self, PK: BLSPubkey, message: bytes, signature: BLSSignature) -> bool:
return self._CoreVerify(PK, PK + message, signature, self.DST)

def AggregateVerify(self, pairs: Iterable[Tuple[BLSPubkey, bytes]],
signature: BLSSignature) -> bool:
pairs = list(pairs)
pairs = [(pk, pk + msg) for pk, msg in pairs]
return self._CoreAggregateVerify(pairs, signature, self.DST)


class G2PoP(BaseG2Ciphersuite):
CarlBeek marked this conversation as resolved.
Show resolved Hide resolved
DST = b'BLS_SIG_BLS12381G2-SHA256-SSWU-RO-_POP_'

def AggregateVerify(self, pairs: Iterable[Tuple[BLSPubkey, bytes]],
signature: BLSSignature) -> bool:
return self._CoreAggregateVerify(pairs, signature, self.DST)

def PopProve(self, SK: int) -> BLSSignature:
pubkey = self.PrivToPub(SK)
return self._CoreSign(SK, pubkey, b'BLS_POP_BLS12381G2-SHA256-SSWU-RO-_POP_')

def PopVerify(self, PK: BLSPubkey, proof: BLSSignature) -> bool:
return self._CoreVerify(PK, PK, proof, b'BLS_POP_BLS12381G2-SHA256-SSWU-RO-_POP_')

@staticmethod
def _AggregatePKs(PKs: Iterable[BLSPubkey]) -> BLSPubkey:
accumulator = Z1 # Seed with the point at infinity
for pk in PKs:
pubkey_point = pubkey_to_G1(pk)
accumulator = add(accumulator, pubkey_point)
return G1_to_pubkey(accumulator)

def FastAggregateVerify(self, PKs: Iterable[BLSPubkey],
message: bytes, signature: BLSSignature) -> bool:
aggregate_pubkey = self._AggregatePKs(PKs)
return self.Verify(aggregate_pubkey, message, signature)
3 changes: 0 additions & 3 deletions py_ecc/bls/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,4 @@
POW_2_382 = 2**382
POW_2_383 = 2**383

# Ciphersuite BLS_SIG_BLS12381G2-SHA256-SSWU-RO_POP_ parameters
DST = b'BLS_SIG_BLS12381G2-SHA256-SSWU-RO_POP_'
HASH_LENGTH_BYTES = 32
HASH_TO_G2_L = 64
61 changes: 61 additions & 0 deletions py_ecc/bls/g2_primatives.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
from eth_typing import (
BLSSignature,
BLSPubkey,
)
from eth_utils import (
big_endian_to_int,
ValidationError,
)

from py_ecc.optimized_bls12_381 import (
is_inf,
multiply,
curve_order,
)

from .point_compression import (
compress_G1,
decompress_G1,
compress_G2,
decompress_G2
)
from .typing import (
G1Compressed,
G1Uncompressed,
G2Compressed,
G2Uncompressed,
)


def subgroup_check(P) -> bool:
return is_inf(multiply(P, curve_order))


def G2_to_signature(pt: G2Uncompressed) -> BLSSignature:
z1, z2 = compress_G2(pt)
return BLSSignature(
z1.to_bytes(48, "big") + z2.to_bytes(48, "big")
)


def signature_to_G2(signature: BLSSignature) -> G2Uncompressed:
p = G2Compressed(
(big_endian_to_int(signature[:48]), big_endian_to_int(signature[48:]))
)
signature_point = decompress_G2(p)
if not subgroup_check(signature_point):
raise ValidationError('Signature is not a part of the E2 subgroup.')
return signature_point


def G1_to_pubkey(pt: G1Uncompressed) -> BLSPubkey:
z = compress_G1(pt)
return BLSPubkey(z.to_bytes(48, "big"))


def pubkey_to_G1(pubkey: BLSPubkey) -> G1Uncompressed:
z = big_endian_to_int(pubkey)
pubkey_point = decompress_G1(G1Compressed(z))
if not subgroup_check(pubkey_point):
raise ValidationError('Pubkey is not a part of the E1 subgroup.')
return pubkey_point
Loading