def decompress_G1(z: G1Compressed) -> G1Uncompressed:
"""
Recovers x and y coordinates from the compressed point.
"""
# b_flag == 1 indicates the infinity point
b_flag = (z % POW_2_383) // POW_2_382
if b_flag == 1:
return Z1
x = z % POW_2_381
# Try solving y coordinate from the equation Y^2 = X^3 + b
# using quadratic residue
y = pow((x**3 + b.n) % q, (q + 1) // 4, q)
if pow(y, 2, q) != (x**3 + b.n) % q:
raise ValueError(
"The given point is not on G1: y**2 = x**3 + b"
)
# Choose the y whose leftmost bit is equal to the a_flag
a_flag = (z % POW_2_382) // POW_2_381
if (y * 2) // q != a_flag:
y = q - y
return (FQ(x), FQ(y), FQ(1))
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)
return decompress_G1(z)
#
# G2
#
def compress_G2(pt: G2Uncompressed) -> G2Compressed:
"""
The compressed point (z1, z2) has the bit order:
z1: (c_flag1, b_flag1, a_flag1, x1)
z2: (c_flag2, b_flag2, a_flag2, x2)
where
- c_flag1 is always set to 1
- b_flag1 indicates infinity when set to 1
- a_flag1 helps determine the y-coordinate when decompressing,
- a_flag2, b_flag2, and c_flag2 are always set to 0
"""
if not is_on_curve(pt, b2):
raise ValueError(
"The given point is not on the twisted curve over FQ**2"
)
if is_inf(pt):
return G2Compressed((POW_2_383 + POW_2_382, 0))
x, y = normalize(pt)
x_re, x_im = x.coeffs
y_re, y_im = y.coeffs
# Record the leftmost bit of y_im to the a_flag1
# If y_im happens to be zero, then use the bit of y_re
a_flag1 = (y_im * 2) // q if y_im > 0 else (y_re * 2) // q
# Imaginary part of x goes to z1, real part goes to z2
# c_flag1 = 1, b_flag1 = 0
z1 = x_im + a_flag1 * POW_2_381 + POW_2_383
# a_flag2 = b_flag2 = c_flag2 = 0
z2 = x_re
return G2Compressed((z1, z2))
def decompress_G2(p: G2Compressed) -> G2Uncompressed:
"""
Recovers x and y coordinates from the compressed point (z1, z2).
"""
z1, z2 = p
# b_flag == 1 indicates the infinity point
b_flag1 = (z1 % POW_2_383) // POW_2_382
if b_flag1 == 1:
return Z2
x1 = z1 % POW_2_381
x2 = z2
# x1 is the imaginary part, x2 is the real part
x = FQ2([x2, x1])
y = modular_squareroot_in_FQ2(x**3 + b2)
if y is None:
raise ValueError("Failed to find a modular squareroot")
# Choose the y whose leftmost bit of the imaginary part is equal to the a_flag1
# If y_im happens to be zero, then use the bit of y_re
a_flag1 = (z1 % POW_2_382) // POW_2_381
y_re, y_im = y.coeffs
if (y_im > 0 and (y_im * 2) // q != a_flag1) or (y_im == 0 and (y_re * 2) // q != a_flag1):
y = FQ2((y * -1).coeffs)
if not is_on_curve((x, y, FQ2([1, 0])), b2):
raise ValueError(
"The given point is not on the twisted curve over FQ**2"
)
return (x, y, FQ2([1, 0]))
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:]))
)
return decompress_G2(p)
https://github.com/ethereum/eth2.0-tests/issues/20 and https://github.com/ethereum/eth2.0-test-generators/pull/23.
The compressed G1 and G2 serialization in the Py-ECC was wrong.
WIP - don't merge.
What was wrong:
Old test vectors available at https://gist.github.com/mratsim/ec15a3d508a486665c7b8c88cf4a6aa5 and new at https://gist.github.com/mratsim/8fe5be09a1910f06e11fc693e25597cd or directly by cloning this branch.
Also the compressed<->uncompressed conversion
https://github.com/ethereum/py_ecc/blob/2088796c59574b256dc8e18f8c9351bc3688ca71/py_ecc/bls/utils.py#L99-L219