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

Kirk baird patch 01 #79

Merged
merged 21 commits into from
Dec 9, 2019
Merged
Show file tree
Hide file tree
Changes from 20 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
34 changes: 18 additions & 16 deletions py_ecc/bls/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,8 @@
multiply,
neg,
pairing,
)
from .typing import (
Domain,
is_inf,
curve_order,
)
from .utils import (
G1_to_pubkey,
Expand All @@ -37,11 +36,10 @@


kirk-baird marked this conversation as resolved.
Show resolved Hide resolved
def sign(message_hash: Hash32,
privkey: int,
domain: Domain) -> BLSSignature:
privkey: int) -> BLSSignature:
return G2_to_signature(
multiply(
hash_to_G2(message_hash, domain),
hash_to_G2(message_hash),
privkey,
))

Expand All @@ -52,17 +50,18 @@ def privtopub(k: int) -> BLSPubkey:

def verify(message_hash: Hash32,
pubkey: BLSPubkey,
signature: BLSSignature,
domain: Domain) -> bool:
signature: BLSSignature) -> bool:
signature_point = signature_to_G2(signature)
if not is_inf(multiply(signature_point, curve_order)):
return False
try:
final_exponentiation = final_exponentiate(
pairing(
signature_to_G2(signature),
signature_point,
G1,
final_exponentiate=False,
) *
pairing(
hash_to_G2(message_hash, domain),
) * pairing(
hash_to_G2(message_hash),
neg(pubkey_to_G1(pubkey)),
final_exponentiate=False,
)
Expand All @@ -88,8 +87,7 @@ def aggregate_pubkeys(pubkeys: Sequence[BLSPubkey]) -> BLSPubkey:

def verify_multiple(pubkeys: Sequence[BLSPubkey],
message_hashes: Sequence[Hash32],
signature: BLSSignature,
domain: Domain) -> bool:
signature: BLSSignature) -> bool:
len_msgs = len(message_hashes)

if len(pubkeys) != len_msgs:
Expand All @@ -99,6 +97,10 @@ def verify_multiple(pubkeys: Sequence[BLSPubkey],
)
)

signature_point = signature_to_G2(signature)
if not is_inf(multiply(signature_point, curve_order)):
return False

try:
o = FQ12([1] + [0] * 11)
for m_pubs in set(message_hashes):
Expand All @@ -108,8 +110,8 @@ def verify_multiple(pubkeys: Sequence[BLSPubkey],
if message_hashes[i] == m_pubs:
group_pub = add(group_pub, pubkey_to_G1(pubkeys[i]))

o *= pairing(hash_to_G2(m_pubs, domain), group_pub, final_exponentiate=False)
o *= pairing(signature_to_G2(signature), neg(G1), final_exponentiate=False)
o *= pairing(hash_to_G2(m_pubs), group_pub, final_exponentiate=False)
o *= pairing(signature_point, neg(G1), final_exponentiate=False)

final_exponentiation = final_exponentiate(o)
return final_exponentiation == FQ12.one()
Expand Down
13 changes: 9 additions & 4 deletions py_ecc/bls/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,18 @@
field_modulus as q,
)

G2_cofactor = 305502333931268344200999753193121504214466019254188142667664032982267604182971884026507427359259977847832272839041616661285803823378372096355777062779109 # noqa: E501
FQ2_order = q ** 2 - 1
eighth_roots_of_unity = tuple(
FQ2([1, 1]) ** ((FQ2_order * k) // 8)
G2_COFACTOR = 305502333931268344200999753193121504214466019254188142667664032982267604182971884026507427359259977847832272839041616661285803823378372096355777062779109 # noqa: E501
FQ2_ORDER = q ** 2 - 1
EIGTH_ROOTS_OF_UNITY = tuple(
FQ2([1, 1]) ** ((FQ2_ORDER * k) // 8)
for k in range(8)
)

POW_2_381 = 2**381
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
38 changes: 30 additions & 8 deletions py_ecc/bls/hash.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,39 @@
import math
import hashlib
import hmac
from typing import Union

from eth_typing import Hash32
from .constants import HASH_LENGTH_BYTES


def hash_eth2(data: Union[bytes, bytearray]) -> Hash32:
def hkdf_extract(salt: Union[bytes, bytearray], ikm: Union[bytes, bytearray]) -> bytes:
"""
Return SHA-256 hashed result.
HKDF-Extract

Note: this API is currently under active research/development so is subject to change
without a major version bump.
https://tools.ietf.org/html/rfc5869
"""
return hmac.new(salt, ikm, hashlib.sha256).digest()


def hkdf_expand(prk: Union[bytes, bytearray], info: Union[bytes, bytearray], length: int) -> bytes:
kirk-baird marked this conversation as resolved.
Show resolved Hide resolved
"""
HKDF-Expand

Note: it's a placeholder and we aim to migrate to a S[T/N]ARK-friendly hash function in
a future Ethereum 2.0 deployment phase.
https://tools.ietf.org/html/rfc5869
"""
return Hash32(hashlib.sha256(data).digest())
n = math.ceil(length / HASH_LENGTH_BYTES)

# okm = T(1) || T(2) || T(3) || ... || T(n)
okm = bytearray(0)
previous = bytearray(0)

for i in range(0, n):
# Concatenate (T(i) || info || i)
text = previous + info + bytes([i + 1])

# T(i + 1) = HMAC(T(i) || info || i)
previous = bytearray(hmac.new(prk, text, hashlib.sha256).digest())
okm.extend(previous)

# Return first `length` bytes.
return okm[:length]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we discard the end of this can we exit early from the loop above once the condition okm >= length is satisfied?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, though okm >= length after n iterators of the loop.

The loop could be changed to the condition while len(okm) < length
Then we would not have to calculate n. However since it is following RFC5869 and that is the way it's mentioned there I think it'd be better to leave it with n.

2 changes: 0 additions & 2 deletions py_ecc/bls/typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,3 @@

G2Uncompressed = Optimized_Point3D[optimized_bls12_381_FQ2]
G2Compressed = NewType('G2Compressed', Tuple[int, int])

Domain = NewType('Domain', bytes) # bytes of length 8
97 changes: 67 additions & 30 deletions py_ecc/bls/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,23 +19,27 @@
field_modulus as q,
is_inf,
is_on_curve,
multiply,
add,
normalize,
optimized_swu_G2,
multiply_clear_cofactor_G2,
iso_map_G2,
)

from .constants import (
POW_2_381,
POW_2_382,
POW_2_383,
FQ2_order,
G2_cofactor,
eighth_roots_of_unity,
FQ2_ORDER,
EIGTH_ROOTS_OF_UNITY,
HASH_TO_G2_L,
DST,
)
from .hash import (
hash_eth2,
hkdf_expand,
hkdf_extract,
)
from .typing import (
Domain,
G1Compressed,
G1Uncompressed,
G2Compressed,
Expand All @@ -55,41 +59,75 @@ def modular_squareroot_in_FQ2(value: FQ2) -> FQ2:
if both solutions have equal imaginary component the value with higher real
component is favored.
"""
candidate_squareroot = value ** ((FQ2_order + 8) // 16)
candidate_squareroot = value ** ((FQ2_ORDER + 8) // 16)
check = candidate_squareroot ** 2 / value
if check in eighth_roots_of_unity[::2]:
x1 = candidate_squareroot / eighth_roots_of_unity[eighth_roots_of_unity.index(check) // 2]
if check in EIGTH_ROOTS_OF_UNITY[::2]:
x1 = candidate_squareroot / EIGTH_ROOTS_OF_UNITY[EIGTH_ROOTS_OF_UNITY.index(check) // 2]
x2 = -x1
x1_re, x1_im = x1.coeffs
x2_re, x2_im = x2.coeffs
return x1 if (x1_im > x2_im or (x1_im == x2_im and x1_re > x2_re)) else x2
return None


def _get_x_coordinate(message_hash: Hash32, domain: Domain) -> FQ2:
# Initial candidate x coordinate
x_re = big_endian_to_int(hash_eth2(message_hash + domain + b'\x01'))
x_im = big_endian_to_int(hash_eth2(message_hash + domain + b'\x02'))
x_coordinate = FQ2([x_re, x_im]) # x_re + x_im * i
def hash_to_G2(message_hash: Hash32) -> G2Uncompressed:
"""
Convert a message to a point on G2 as defined here:
https://tools.ietf.org/html/draft-irtf-cfrg-hash-to-curve-05#section-3

return x_coordinate
Contants and inputs follow the ciphersuite ``BLS12381G2-SHA256-SSWU-RO-`` defined here:
https://tools.ietf.org/html/draft-irtf-cfrg-hash-to-curve-05#section-8.9.2
"""
u0 = hash_to_base_FQ2(message_hash, 0)
u1 = hash_to_base_FQ2(message_hash, 1)
q0 = map_to_curve_G2(u0)
q1 = map_to_curve_G2(u1)
r = add(q0, q1)
p = clear_cofactor_G2(r)
return p
kirk-baird marked this conversation as resolved.
Show resolved Hide resolved


def hash_to_G2(message_hash: Hash32, domain: Domain) -> G2Uncompressed:
x_coordinate = _get_x_coordinate(message_hash, domain)
def hash_to_base_FQ2(message_hash: Hash32, ctr: int) -> FQ2:
"""
Hash To Base for FQ2

# Test candidate y coordinates until a one is found
while 1:
y_coordinate_squared = x_coordinate ** 3 + FQ2([4, 4]) # The curve is y^2 = x^3 + 4(i + 1)
y_coordinate = modular_squareroot_in_FQ2(y_coordinate_squared)
if y_coordinate is not None: # Check if quadratic residue found
break
x_coordinate += FQ2([1, 0]) # Add 1 and try again
Convert a message to a point in the finite field as defined here:
https://tools.ietf.org/html/draft-irtf-cfrg-hash-to-curve-05#section-5
"""
m_prime = hkdf_extract(DST, message_hash + b'\x00')
info_pfx = b'H2C' + bytes([ctr])
e = []

return multiply(
(x_coordinate, y_coordinate, FQ2([1, 0])),
G2_cofactor,
)
# for i in (1, ..., m), where m is the extension degree of FQ2
for i in range(1, 3):
kirk-baird marked this conversation as resolved.
Show resolved Hide resolved
info = info_pfx + bytes([i])
t = hkdf_expand(m_prime, info, HASH_TO_G2_L)
e.append(big_endian_to_int(t))

return FQ2(e)


def map_to_curve_G2(u: FQ2) -> G2Uncompressed:
"""
Map To Curve for G2

First, convert FQ2 point to a point on the 3-Isogeny curve.
SWU Map: https://tools.ietf.org/html/draft-irtf-cfrg-hash-to-curve-05#section-6.6.2

Second, map 3-Isogeny curve to BLS12-381-G2 curve.
3-Isogeny Map: https://tools.ietf.org/html/draft-irtf-cfrg-hash-to-curve-05#appendix-C.3
"""
(x, y, z) = optimized_swu_G2(u)
return iso_map_G2(x, y, z)


def clear_cofactor_G2(p: G2Uncompressed) -> G2Uncompressed:
"""
Clear Cofactor via Multiplication

Ensure a point falls in the correct sub group of the curve.
"""
return multiply_clear_cofactor_G2(p)


#
Expand Down Expand Up @@ -221,8 +259,7 @@ def decompress_G2(p: G2Compressed) -> G2Uncompressed:
def G2_to_signature(pt: G2Uncompressed) -> BLSSignature:
z1, z2 = compress_G2(pt)
return BLSSignature(
z1.to_bytes(48, "big") +
z2.to_bytes(48, "big")
z1.to_bytes(48, "big") + z2.to_bytes(48, "big")
)


Expand Down
2 changes: 1 addition & 1 deletion py_ecc/fields/field_elements.py
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ class FQP(object):

def __init__(self,
coeffs: Sequence[IntOrFQ],
modulus_coeffs: Sequence[IntOrFQ]=None) -> None:
modulus_coeffs: Sequence[IntOrFQ] = None) -> None:
if self.field_modulus is None:
raise AttributeError("Field Modulus hasn't been specified")

Expand Down
44 changes: 43 additions & 1 deletion py_ecc/fields/optimized_field_elements.py
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,21 @@ def __repr__(self: T_FQ) -> str:
def __int__(self: T_FQ) -> int:
return self.n

def sgn0_be(self: T_FQ) -> int:
"""
Calculates the sign of a value.
sgn0_be(x) = -1 when x > -x

Defined here:
https://tools.ietf.org/html/draft-irtf-cfrg-hash-to-curve-05#section-4.1.1
"""
if self.n == 0:
return 1
neg = type(self)(-self)
if neg.n > self.n:
return 1
return -1

@classmethod
def one(cls: Type[T_FQ]) -> T_FQ:
return cls(1)
Expand All @@ -201,7 +216,7 @@ class FQP(object):

def __init__(self,
coeffs: Sequence[IntOrFQ],
modulus_coeffs: Sequence[IntOrFQ]=None) -> None:
modulus_coeffs: Sequence[IntOrFQ] = None) -> None:
if self.field_modulus is None:
raise AttributeError("Field Modulus hasn't been specified")

Expand Down Expand Up @@ -363,6 +378,33 @@ def __ne__(self: T_FQP, other: T_FQP) -> bool: # type: ignore # https://gith
def __neg__(self: T_FQP) -> T_FQP:
return type(self)([-c for c in self.coeffs])

def sgn0_be(self: T_FQP) -> int:
"""
Calculates the sign of a value.
sgn0_be(x) = -1 when x > -x

Defined here:
https://tools.ietf.org/html/draft-irtf-cfrg-hash-to-curve-05#section-4.1.1
"""
sign = 0
for x_i in reversed(self.coeffs):
sign_i = 0
if isinstance(x_i, int):
if x_i == 0:
sign_i = 0
elif (-x_i % self.field_modulus) > (x_i % self.field_modulus):
sign_i = 1
else:
sign_i = -1
elif isinstance(x_i, FQ):
sign_i = x_i.sgn0_be()
else:
kirk-baird marked this conversation as resolved.
Show resolved Hide resolved
raise TypeError("Only int and T_FQ types are accepted: got {type(x_i)}")

if sign == 0:
sign = sign_i
return sign

@classmethod
def one(cls: Type[T_FQP]) -> T_FQP:
return cls([1] + [0] * (cls.degree - 1))
Expand Down
7 changes: 7 additions & 0 deletions py_ecc/optimized_bls12_381/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,10 @@
pairing,
final_exponentiate,
)
from .optimized_swu import ( # noqa: F401
optimized_swu_G2,
iso_map_G2,
)
from .optimized_clear_cofactor import ( # noqa: F401
multiply_clear_cofactor_G2,
)
Loading