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 3 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
18 changes: 6 additions & 12 deletions py_ecc/bls/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,6 @@
neg,
pairing,
)
from .typing import (
Domain,
)
from .utils import (
G1_to_pubkey,
G2_to_signature,
Expand All @@ -37,11 +34,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,8 +48,7 @@ def privtopub(k: int) -> BLSPubkey:

def verify(message_hash: Hash32,
pubkey: BLSPubkey,
signature: BLSSignature,
domain: Domain) -> bool:
signature: BLSSignature) -> bool:
try:
final_exponentiation = final_exponentiate(
pairing(
Expand All @@ -62,7 +57,7 @@ def verify(message_hash: Hash32,
final_exponentiate=False,
) *
pairing(
hash_to_G2(message_hash, domain),
hash_to_G2(message_hash),
neg(pubkey_to_G1(pubkey)),
final_exponentiate=False,
)
Expand All @@ -88,8 +83,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 @@ -108,7 +102,7 @@ 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(hash_to_G2(m_pubs), group_pub, final_exponentiate=False)
o *= pairing(signature_to_G2(signature), neg(G1), final_exponentiate=False)

final_exponentiation = final_exponentiate(o)
Expand Down
10 changes: 6 additions & 4 deletions py_ecc/bls/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,16 @@
from py_ecc.optimized_bls12_381 import (
field_modulus as q,
)
from py_ecc.optimized_bls12_381.constants import eighth_roots_of_unity

G2_cofactor = 305502333931268344200999753193121504214466019254188142667664032982267604182971884026507427359259977847832272839041616661285803823378372096355777062779109 # noqa: E501
FQ2_order = q ** 2 - 1
eighth_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 BLS12381G2-SHA256-SSWU-RO paramters
DST = b'BLS12381G2-SHA256-SSWU-RO' # UTF-8 0x424c53313233383147322d5348413235362d535357552d524f
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 = bytes(0)
previous = bytes(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 = hmac.new(prk, text, hashlib.sha256).digest()
okm = okm + previous
kirk-baird marked this conversation as resolved.
Show resolved Hide resolved

# 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
81 changes: 59 additions & 22 deletions py_ecc/bls/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,11 @@
is_on_curve,
multiply,
normalize,
optimized_swu_G2,
iso_map_G2,
)
#from py_ecc.optimized_swu import (
#)

from .constants import (
POW_2_381,
Expand All @@ -30,12 +34,15 @@
FQ2_order,
G2_cofactor,
eighth_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 Down Expand Up @@ -66,31 +73,62 @@ def modular_squareroot_in_FQ2(value: FQ2) -> FQ2:
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 = q0 + q1
# TODO: Implement the following functions
# p = clear_cofactor_G2(r)
return r

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])[:1] + bytes([i])[:1]
kirk-baird marked this conversation as resolved.
Show resolved Hide resolved
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 Psi Method

Ensure a point falls in the correct sub group of the curve.
See Section 4.1 of https://eprint.iacr.org/2017/419
"""
# TODO: implement this function
return p

#
# G1
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
27 changes: 27 additions & 0 deletions py_ecc/fields/optimized_field_elements.py
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,15 @@ def __repr__(self: T_FQ) -> str:
def __int__(self: T_FQ) -> int:
return self.n

def sgn0(self: Type[T_FQ]) -> int:
if self.n == 0:
return 0
kirk-baird marked this conversation as resolved.
Show resolved Hide resolved
neg = -self
kirk-baird marked this conversation as resolved.
Show resolved Hide resolved
if neg.n > self.n:
return 1
else:
return -1

@classmethod
def one(cls: Type[T_FQ]) -> T_FQ:
return cls(1)
Expand Down Expand Up @@ -363,6 +372,24 @@ 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: Type[T_FQP]) -> int:
sign = 0
# TODO: Confirm coeffs is type FQ or FQP
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
else:
kirk-baird marked this conversation as resolved.
Show resolved Hide resolved
sign_i = x_i.sgn0()
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
4 changes: 4 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,7 @@
pairing,
final_exponentiate,
)
from .optimized_swu import ( # noqa: F401
optimized_swu_G2,
iso_map_G2,
)
50 changes: 50 additions & 0 deletions py_ecc/optimized_bls12_381/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
from py_ecc.fields import (
optimized_bls12_381_FQ2 as FQ2,
)

# Ciphersuite BLS12381G2-SHA256-SSWU-RO paramters
kirk-baird marked this conversation as resolved.
Show resolved Hide resolved
ISO_3_A = FQ2([0, 240])
ISO_3_B = FQ2([1012, 1012])
ISO_3_Z = FQ2([1, 1])
P_MINUS_9_DIV_16 = 1001205140483106588246484290269935788605945006208159541241399033561623546780709821462541004956387089373434649096260670658193992783731681621012512651314777238193313314641988297376025498093520728838658813979860931248214124593092835 # noqa: E501
SQRT_I = 1028732146235106349975324479215795277384839936929757896155643118032610843298655225875571310552543014690878354869257 # noqa: E501
Copy link
Member

Choose a reason for hiding this comment

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

I have some discomfort from having all of these constants floating around. Is there a formal/canonical spec that we can include a link to so that it's easy to reference/cross-check these constants are indeed the values they are supposed to be?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There are soo many constants! Unfortunately they are necessary for the isogeny mapping.

Most of them come from
BLS General Constants: https://tools.ietf.org/html/draft-irtf-cfrg-hash-to-curve-04#section-8.7
ISO constants: https://tools.ietf.org/html/draft-irtf-cfrg-hash-to-curve-04#appendix-C.2

EV1 = 426061185569912361983521454249761337083267257081408520893788542915383290290183480196466860748572708974347122096641 # noqa: E501
EV2 = 1288825690270127928862280779549770369920038885627059587892741294824265852728860506840371064237610802480748737721626 # noqa: E501

positive_eigth_roots_of_unity = (
FQ2([1, 0]), FQ2([0, 1]), FQ2([SQRT_I, SQRT_I]), FQ2([SQRT_I, -SQRT_I]),
)
eighth_roots_of_unity = (
kirk-baird marked this conversation as resolved.
Show resolved Hide resolved
FQ2([1, 0]), FQ2([0, 1]), FQ2([SQRT_I, SQRT_I]), FQ2([SQRT_I, -SQRT_I]),
-FQ2([1, 0]), -FQ2([0, 1]), -FQ2([SQRT_I, SQRT_I]), -FQ2([SQRT_I, -SQRT_I]),
)

# X Numerator
ISO_3_K_1_0 = FQ2([889424345604814976315064405719089812568196182208668418962679585805340366775741747653930584250892369786198727235542, 889424345604814976315064405719089812568196182208668418962679585805340366775741747653930584250892369786198727235542])
ISO_3_K_1_1 = FQ2([0, 2668273036814444928945193217157269437704588546626005256888038757416021100327225242961791752752677109358596181706522])
ISO_3_K_1_2 = FQ2([2668273036814444928945193217157269437704588546626005256888038757416021100327225242961791752752677109358596181706526, 1334136518407222464472596608578634718852294273313002628444019378708010550163612621480895876376338554679298090853261])
ISO_3_K_1_3 = FQ2([3557697382419259905260257622876359250272784728834673675850718343221361467102966990615722337003569479144794908942033, 0])
ISO_3_X_NUMERATOR = (ISO_3_K_1_0, ISO_3_K_1_1, ISO_3_K_1_2, ISO_3_K_1_3)

# X Denominator
ISO_3_K_2_0 = FQ2([0, 4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272559715])
ISO_3_K_2_1 = FQ2([12, 4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272559775])
ISO_3_K_2_2 = FQ2.one()
ISO_3_K_2_3 = FQ2.zero()
ISO_3_X_DENOMINATOR = (ISO_3_K_2_0, ISO_3_K_2_1, ISO_3_K_2_2, ISO_3_K_2_3)

# Y Numerator
ISO_3_K_3_0 = FQ2([3261222600550988246488569487636662646083386001431784202863158481286248011511053074731078808919938689216061999863558, 3261222600550988246488569487636662646083386001431784202863158481286248011511053074731078808919938689216061999863558])
ISO_3_K_3_1 = FQ2([0, 889424345604814976315064405719089812568196182208668418962679585805340366775741747653930584250892369786198727235518])
ISO_3_K_3_2 = FQ2([2668273036814444928945193217157269437704588546626005256888038757416021100327225242961791752752677109358596181706524, 1334136518407222464472596608578634718852294273313002628444019378708010550163612621480895876376338554679298090853263])
ISO_3_K_3_3 = FQ2([2816510427748580758331037284777117739799287910327449993381818688383577828123182200904113516794492504322962636245776, 0])
ISO_3_Y_NUMERATOR = (ISO_3_K_3_0, ISO_3_K_3_1, ISO_3_K_3_2, ISO_3_K_3_3)

# Y Denominator
ISO_3_K_4_0 = FQ2([4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272559355, 4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272559355])
ISO_3_K_4_1 = FQ2([0, 4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272559571])
ISO_3_K_4_2 = FQ2([18, 4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272559769])
ISO_3_K_4_3 = FQ2.one()
ISO_3_Y_DENOMINATOR = (ISO_3_K_4_0, ISO_3_K_4_1, ISO_3_K_4_2, ISO_3_K_4_3)

ISO_3_MAP_COEFFICIENTS = (ISO_3_X_NUMERATOR, ISO_3_X_DENOMINATOR, ISO_3_Y_NUMERATOR, ISO_3_Y_DENOMINATOR)
Loading