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 12 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
35 changes: 18 additions & 17 deletions py_ecc/bls/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,7 @@
multiply,
neg,
pairing,
)
from .typing import (
Domain,
is_inf,
)
from .utils import (
G1_to_pubkey,
Expand All @@ -34,14 +32,13 @@
pubkey_to_G1,
signature_to_G2,
)

from .constants import G2_COFACTOR

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 +49,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, G2_COFACTOR)):
kirk-baird marked this conversation as resolved.
Show resolved Hide resolved
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 +86,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 +96,10 @@ def verify_multiple(pubkeys: Sequence[BLSPubkey],
)
)

signature_point = signature_to_G2(signature)
if not is_inf(multiply(signature_point, G2_COFACTOR)):
kirk-baird marked this conversation as resolved.
Show resolved Hide resolved
return False

try:
o = FQ12([1] + [0] * 11)
for m_pubs in set(message_hashes):
Expand All @@ -108,8 +109,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_ paramters
kirk-baird marked this conversation as resolved.
Show resolved Hide resolved
DST = b'BLS_SIG_BLS12381G2-SHA256-SSWU-RO_POP_'
HASH_LENGTH_BYTES = 32
HASH_TO_G2_L = 64
39 changes: 39 additions & 0 deletions py_ecc/bls/hash.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
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:
kirk-baird marked this conversation as resolved.
Show resolved Hide resolved
"""
Expand All @@ -15,3 +18,39 @@ def hash_eth2(data: Union[bytes, bytearray]) -> Hash32:
a future Ethereum 2.0 deployment phase.
"""
return Hash32(hashlib.sha256(data).digest())


def hkdf_extract(salt: Union[bytes, bytearray], ikm: Union[bytes, bytearray]) -> bytes:
"""
HKDF-Expand

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

https://tools.ietf.org/html/rfc5869
"""
# n = cieling(length / HASH_LENGTH_BYTES)
n = length // HASH_LENGTH_BYTES
kirk-baird marked this conversation as resolved.
Show resolved Hide resolved
if n * HASH_LENGTH_BYTES < length:
kirk-baird marked this conversation as resolved.
Show resolved Hide resolved
n += 1

# 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
96 changes: 66 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,74 @@ 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:
return hash_to_curve_G2(message_hash)

return x_coordinate

def hash_to_curve_G2(message_hash: Hash32) -> G2Uncompressed:
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)

# 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
def hash_to_base_FQ2(message_hash: Hash32, ctr: int) -> FQ2:
"""
Hash To Base for FQ2

return multiply(
(x_coordinate, y_coordinate, FQ2([1, 0])),
G2_cofactor,
)
Converts a message to a point in the finite field as defined here:
kirk-baird marked this conversation as resolved.
Show resolved Hide resolved
https://tools.ietf.org/html/draft-irtf-cfrg-hash-to-curve-04#section-5
"""
m_prime = hkdf_extract(DST, message_hash)
e = []

for i in range(1, 3):
kirk-baird marked this conversation as resolved.
Show resolved Hide resolved
# Concatenate ("H2C" || I2OSP(ctr, 1) || I2OSP(i, 1))
info = b'H2C' + bytes([ctr]) + 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-04#section-6.5.2

Second, map 3-Isogeny curve to BLS381-G2 curve.
kirk-baird marked this conversation as resolved.
Show resolved Hide resolved
3-Isogeny Map: https://tools.ietf.org/html/draft-irtf-cfrg-hash-to-curve-04#appendix-C.2
"""
(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.
Optimization by applying Section 4.1 of https://eprint.iacr.org/2017/419
However `US patent 7110538` covers the optimization and so it may not
be able to be applied.
"""
return multiply_clear_cofactor_G2(p)


#
Expand Down Expand Up @@ -221,8 +258,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
30 changes: 29 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,14 @@ def __repr__(self: T_FQ) -> str:
def __int__(self: T_FQ) -> int:
return self.n

def sgn0(self: T_FQ) -> int:
kirk-baird marked this conversation as resolved.
Show resolved Hide resolved
if self.n == 0:
return 0
kirk-baird marked this conversation as resolved.
Show resolved Hide resolved
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 +209,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 +371,26 @@ 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(self: T_FQP) -> int:
kirk-baird marked this conversation as resolved.
Show resolved Hide resolved
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()
else:
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