From 18c438764d5c743806356396fb53078e78770d70 Mon Sep 17 00:00:00 2001 From: Carl Beekhuizen Date: Wed, 18 Mar 2020 17:01:53 +0100 Subject: [PATCH 01/12] Switches hash-to-field to new v06 hash system --- py_ecc/bls/constants.py | 4 ++- py_ecc/bls/hash.py | 39 +++++++++++++++++++++++---- py_ecc/bls/hash_to_curve.py | 48 +++++++++++++++------------------ tests/bls/test_hash_to_curve.py | 33 +++++++++-------------- 4 files changed, 70 insertions(+), 54 deletions(-) diff --git a/py_ecc/bls/constants.py b/py_ecc/bls/constants.py index 55f01e9c..03ab8b08 100644 --- a/py_ecc/bls/constants.py +++ b/py_ecc/bls/constants.py @@ -16,4 +16,6 @@ POW_2_382 = 2**382 POW_2_383 = 2**383 -HASH_TO_G2_L = 64 +HASH_TO_FIELD_L = 64 +HASH_TO_FIELD_B_IN_BYTES = 32 +HASH_TO_FIELD_R_IN_BYTES = 64 diff --git a/py_ecc/bls/hash.py b/py_ecc/bls/hash.py index 8e675090..323bd8a4 100644 --- a/py_ecc/bls/hash.py +++ b/py_ecc/bls/hash.py @@ -1,8 +1,15 @@ -import math -import hashlib import hmac -from typing import Union +import math +from typing import ( + List, + Union, +) +from hashlib import sha256 as _sha256 +from .constants import ( + HASH_TO_FIELD_B_IN_BYTES, + HASH_TO_FIELD_R_IN_BYTES, +) def hkdf_extract(salt: Union[bytes, bytearray], ikm: Union[bytes, bytearray]) -> bytes: """ @@ -10,7 +17,7 @@ def hkdf_extract(salt: Union[bytes, bytearray], ikm: Union[bytes, bytearray]) -> https://tools.ietf.org/html/rfc5869 """ - return hmac.new(salt, ikm, hashlib.sha256).digest() + return hmac.new(salt, ikm, _sha256).digest() def hkdf_expand(prk: Union[bytes, bytearray], info: Union[bytes, bytearray], length: int) -> bytes: @@ -30,8 +37,30 @@ def hkdf_expand(prk: Union[bytes, bytearray], info: Union[bytes, bytearray], len text = previous + info + bytes([i + 1]) # T(i + 1) = HMAC(T(i) || info || i) - previous = bytearray(hmac.new(prk, text, hashlib.sha256).digest()) + previous = bytearray(hmac.new(prk, text, _sha256).digest()) okm.extend(previous) # Return first `length` bytes. return okm[:length] + +def sha256(x: bytes) -> bytes: + m = _sha256() + m.update(x) + return m.digest() + + +def xor(a: bytes, b: bytes) -> bytes: + return bytes(_a ^ _b for _a, _b in zip(a, b)) + + +def expand_message_xmd(msg: bytes, DST: bytes, len_in_bytes: int) -> bytes: + ell = math.ceil(len_in_bytes / HASH_TO_FIELD_B_IN_BYTES) + DST_prime = len(DST).to_bytes(1, 'big') + DST + Z_pad = b'\x00' * HASH_TO_FIELD_R_IN_BYTES + l_i_b_str = len_in_bytes.to_bytes(2, 'big') + b_0 = sha256(Z_pad + msg + l_i_b_str + b'\x00' + DST_prime) + b = [sha256(b_0 + b'\x01' + DST_prime)] + for i in range(2, ell + 1): + b.append(sha256(xor(b_0, b[i - 2]) + i.to_bytes(1, 'big') + DST_prime)) + pseudo_random_bytes = b''.join(b) + return pseudo_random_bytes[:len_in_bytes] diff --git a/py_ecc/bls/hash_to_curve.py b/py_ecc/bls/hash_to_curve.py index ca11c157..5f697e02 100644 --- a/py_ecc/bls/hash_to_curve.py +++ b/py_ecc/bls/hash_to_curve.py @@ -1,25 +1,18 @@ -from eth_utils import ( - big_endian_to_int, -) - +from typing import List from py_ecc.fields import ( optimized_bls12_381_FQ2 as FQ2, ) from py_ecc.optimized_bls12_381 import ( add, iso_map_G2, + field_modulus, multiply_clear_cofactor_G2, optimized_swu_G2, ) -from .constants import HASH_TO_G2_L -from .typing import ( - G2Uncompressed, -) -from .hash import ( - hkdf_expand, - hkdf_extract, -) +from .constants import HASH_TO_FIELD_L +from .hash import expand_message_xmd +from .typing import G2Uncompressed # Hash to G2 @@ -31,8 +24,7 @@ def hash_to_G2(message: bytes, DST: bytes) -> G2Uncompressed: 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, 0, DST) - u1 = hash_to_base_FQ2(message, 1, DST) + u0, u1 = hash_to_field_FQ2(message, 2, DST) q0 = map_to_curve_G2(u0) q1 = map_to_curve_G2(u1) r = add(q0, q1) @@ -40,24 +32,26 @@ def hash_to_G2(message: bytes, DST: bytes) -> G2Uncompressed: return p -def hash_to_base_FQ2(message: bytes, ctr: int, DST: bytes) -> FQ2: +def hash_to_field_FQ2(message: bytes, count: int, DST: bytes) -> List[FQ2]: """ - Hash To Base for FQ2 + Hash To Base Field for FQ2 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 + b'\x00') - info_pfx = b'H2C' + bytes([ctr]) - e = [] - - # for i in (1, ..., m), where m is the extension degree of FQ2 - for i in range(1, 3): - 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) + M = 2 # m is the extension degree of FQ2 + len_in_bytes = count * M * HASH_TO_FIELD_L + pseudo_random_bytes = expand_message_xmd(message, DST, len_in_bytes) + u: List[FQ2] = [] + for i in range(0, count): + e: List[int] = [] + for j in range(0, M): + elem_offset = HASH_TO_FIELD_L * (j + i * M) + tv = pseudo_random_bytes[elem_offset: elem_offset + HASH_TO_FIELD_L] + e.append(int.from_bytes(tv, 'big') % field_modulus) + u.append(FQ2(e)) + + return u def map_to_curve_G2(u: FQ2) -> G2Uncompressed: diff --git a/tests/bls/test_hash_to_curve.py b/tests/bls/test_hash_to_curve.py index 07b8befe..b1844f13 100644 --- a/tests/bls/test_hash_to_curve.py +++ b/tests/bls/test_hash_to_curve.py @@ -31,7 +31,7 @@ iso_map_G2, ) -DST = b'BLS_SIG_BLS12381G2-SHA256-SSWU-RO_POP_' # TODO: Switch out test for valid DST +DST = b'BLS12381G2_XMD:SHA-256_SSWU_RO_TESTGEN' @pytest.mark.parametrize( @@ -62,27 +62,18 @@ def test_iso_map_G2(iso_x, iso_y, iso_z, g2_x, g2_y): @pytest.mark.parametrize( 'msg,x,y', [ - (b'msg', - FQ2([int('7896efdac56b0f6cbd8c78841676d63fc733b692628687bf25273aa8a107bd8cb53bbdb705b551e239dffe019abd4df', 16), int('bd557eda8d16ab2cb2e71cca4d7b343985064daad04734e07da5cdda26610b59cdc0810a25276467d24b315bf7860e0', 16)]), - FQ2([int('1bdb6290cae9f30f263dd40f014b9f4406c3fbbc5fea47e2ebd45e42332553961eb53a15c09e5e090d7a7122dc6657', 16), int('18370459c44e799af8ef31634a683e340e79c3a06f912594d287a443620933b47a2a3e5ce4470539eae50f6d49b8ebd6', 16)])), - (b'01234567890123456789012345678901', - FQ2([int('16b7456df1dfa411b8be80c503864b0795b0b9a7674c05c00e7bdee5a75cbdeec633e16a104406ea626ea6845f5d19b5', 16), int('12ae54eeb3b4dc113d7e80302e51456224087955910479929bf912d89177aa050376960002a96fc6541ac041957f4b93', 16)]), - FQ2([int('1632fe9d91a984f30a7d9b3bab6583974a2ca55933d96cba85f39ddd61ea0129274f75ad7de29473adf3db676dcdb6a3', 16), int('8d5d3b670fca3661122b0ca5929e48f293a5a5c1261050c46b6a08eac3f7d1f5075e2139a63f98e717ecc7c2e00d042', 16)])), (b'', - FQ2([int('c38e18c9ca92ad387cbfa0e9bd62e53e4f938006097a092d5e9f2c6f3963d78969e7631bf8d6a8a9aad36bc82d763c1', 16), int('23ebc431b239ee7606aad7cd4eee60bb70df3e5072ede86946ffaddb0584e1fcfcee9484869f41e09ab4d64b9e4a72a', 16)]), - FQ2([int('735ae5ca4a2320d820e15501ee79c42ff58a6f40e8549eada554e07c94b34b6634b6034f8735a7e4ac01db81b00f58e', 16), int('1687b6a2fb9e542426d508d4a58846c0e3496ede2e12f57f3358110874ba0011e2107e0742eeb6707682d5ddf319b6f6', 16)])), - (b'abcdefghijklmnopqrstuvwxyz', - FQ2([int('db85d0c792c586c6efb4126e98a8a8788d28187a6432cbdd57444a8c937ce20e0fc0774477150d31bfff83a050b530e', 16), int('13505f5cbb1503c7b1206edd31364a467f5159d741cffe8f443f2282b4adfcf5f1450bd2fe6127991ff60955b3b40015', 16)]), - FQ2([int('1738e4903e5618fcba965861c73d7c7a7544fabc9762ccdf9842dbba30566ce33047c3ff714ce8a10323bcac0ee88479', 16), int('d0df337706a8b4c367ea189d9e213f47455399ddf734358695e84ad09630a724082ad22dda74e6cd41378dbb89b0ebd', 16)])), - (b'\xff' * 100, - FQ2([int('a2b9bb7afda6e1c3cb2340aa679ce00469a14c651becd30fa231c83ab82d1b92db074058c3673daaaa2a113f0c3ea56', 16), int('e7fcdf25cf4465f58de593bf6445ec1cd164de346a27ed46314dcbb35a830650f5bb4d8049878d9a84a34013fa4fb11', 16)]), - FQ2([int('14f909c9fb9fb14c0e7455ed5306edaad40e7c57cdd719f59730db6ae64161db1f1a8159db4d97700fba7547920fe1a2', 16), int('1702a797d33e0c7b3fac012da0ef1960e0f4551f23ffee3e12dc36ac4acdd6d78bee97bad76689b8e70dac80449a626c', 16)])), - (b'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855', - FQ2([int('7e2b8b61562339640addfda3202f5d657aa77c143bf5bceda818525ba6f984eba2648528928d6c9680f752dd88d91e3', 16), int('1663cd7231bd9708bebe0be61baecf2b89ebaa658150696f5be2dbe0e092ec698c931e8795ac6319f1c5fdda5d14136a', 16)]), - FQ2([int('33d40a6eb88c11c6018fcc00489bc4b9dd700c20d1bab21ad463c5ee63ce671d199020ba743828d450da050f0385680', 16), int('10336a533d1e3564da20bffe87ebc82121cfbfad2e36ecb950c5e12d8552bf932f5f5e846a50e9706b21b0db6585a777', 16)])), - (b'e46b320165eec91e6344fa10340d5b3208304d6cad29d0d5aed18466d1d9d80e', - FQ2([int('119a71e0d20489cf8c5f82c51e879e7b344e53307b53be650df7f3f04907b75b71fdafe26e8d4e14e603440b09efe6f3', 16), int('e4ea193377da29537e8fe6f6f631adff10afaef2ea1eb2107d30d97358c1a19975e0a8bb62650ff90447cf5b3719c1d', 16)]), - FQ2([int('1142f2e077cc4230ee3cf07565ee626141ea9b86a79a0422d7f0e84c281ca09c5bbbe95f21e51285618c81d6dfda943d', 16), int('15e4da056552343eb519b3087962521d112c7e307d731373e8f1c72415306ccbc3c14fc6d68d61d2feeda3ea2e7729f', 16)])), + FQ2([0x0d3b02ee071b12d1e79138c3900ca3da7b8021ac462fe6ed68080dc9a5f1c5de46b7fe171e8b3e4e7537e7746757aeca, 0x0d4733459fead6a1f30e5f92df08ecfd0db9bcd0f3e2f2de0f00c8f45e081420aa4392eade61eade57d7a68474672fc1]), + FQ2([0x09cc6f7b3074f0c82510e65d8fc58f6033e03ba7358005a13e2bbd7f429b080f29731ef08c3780c9e3c746578b96b05c, 0x0011531b8e08900a4f6f612e1e27432961419ce6a5ee3ec904a53588982d36ec4ea37be80b6cb7d986b38faec67dbe44])), + (b'abc', + FQ2([0x0b6d276d0bfbddde617a9ab4c175b07c9c4aecad2cdd6cc9ca541b61334a69c58680ef5692bbad03d2f572838df32b66, 0x139e9d78ff6d9d163f979d14a64c5e57f82f1ef7e42ece338b571a9e92c0666f0f6bf1a5fc21e2d32bcb6432eab7037c]), + FQ2([0x022f9ee5d596d06c5f2f735c3c5f743978f79fd57bf7d4291e221227f490d3f276066de9f9edc89c57e048ef4cf0ef72, 0x14dd23517516a80d1d840e34f51dfb76946c7670fca0f36ad8ec9bde4ea82dfae119a21b076519bcc1c00152989a4d45])), + (b'abcdef0123456789', + FQ2([0x0ded52c30aace28d3e9cc5c1b47861ae4dd4e9cd17622e0f5b9d584af0397cd0e3bae80d4ee2d9d4b18c390f63154dfd, 0x046701a03f361a0b8392ca387585f7ee6534dcec9450a035e39dc37387d5ca079b9557447f7d9cad0bd9671cb65ada02]), + FQ2([0x07a5cf56c5ea1d69ad59c0e80cc16c0c1b27f02840b396eb0ea320f70e87f705c6fa70cfeb9719b14badbb058bec5a4c, 0x0674d1f7c9e8e84d8d7a07b40231257571c43160fd566e8d24459d17ca52f6068e1b63aaae5359d8869d4abc66de66b6])), + (b'a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', + FQ2([0x0161130ef4aa2f60f751e6b3dd48ac6e994d2d2613897c5dd26945bc72f33cc2977e1255c3f2dc0f1440d15a71c29b40, 0x06db1818f132a61f5fe86d315faa8de4653049ac9cf7fbbc6d9987e5864d82a0156259d56192109bafddd5c30b9f01f5]), + FQ2([0x00f7fab0fedc978b974a38a1755244727b8a4eb31073653fa949594645ad181880d20ff0c91c4375b7e451fe803c9847, 0x0964d550ee8752b6db99555ffcd442b4185267f31e3d57435ea73896a7a9fe952bd67f90fd75f4413212ac9640a7672c])), ] ) def test_hash_to_G2(msg, x, y): From eb39f3eaee5b0be3155cf949e9b99eecbdae7351 Mon Sep 17 00:00:00 2001 From: Carl Beekhuizen Date: Wed, 18 Mar 2020 18:11:36 +0100 Subject: [PATCH 02/12] Linting fixes --- py_ecc/bls/hash.py | 7 +++---- tests/bls/test_hash_to_curve.py | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/py_ecc/bls/hash.py b/py_ecc/bls/hash.py index 323bd8a4..38593e0f 100644 --- a/py_ecc/bls/hash.py +++ b/py_ecc/bls/hash.py @@ -1,9 +1,6 @@ import hmac import math -from typing import ( - List, - Union, -) +from typing import Union from hashlib import sha256 as _sha256 from .constants import ( @@ -11,6 +8,7 @@ HASH_TO_FIELD_R_IN_BYTES, ) + def hkdf_extract(salt: Union[bytes, bytearray], ikm: Union[bytes, bytearray]) -> bytes: """ HKDF-Extract @@ -43,6 +41,7 @@ def hkdf_expand(prk: Union[bytes, bytearray], info: Union[bytes, bytearray], len # Return first `length` bytes. return okm[:length] + def sha256(x: bytes) -> bytes: m = _sha256() m.update(x) diff --git a/tests/bls/test_hash_to_curve.py b/tests/bls/test_hash_to_curve.py index b1844f13..0efcad90 100644 --- a/tests/bls/test_hash_to_curve.py +++ b/tests/bls/test_hash_to_curve.py @@ -59,6 +59,7 @@ def test_iso_map_G2(iso_x, iso_y, iso_z, g2_x, g2_y): assert g2_y == result_y +# Tests taken from: https://github.com/cfrg/draft-irtf-cfrg-hash-to-curve/blob/master/draft-irtf-cfrg-hash-to-curve.md#bls12381g2_xmdsha-256_sswu_ro_ @pytest.mark.parametrize( 'msg,x,y', [ @@ -78,7 +79,6 @@ def test_iso_map_G2(iso_x, iso_y, iso_z, g2_x, g2_y): ) def test_hash_to_G2(msg, x, y): point = hash_to_G2(msg, DST) - assert is_on_curve(point, b2) # Affine From 9e98f81906ee1b3b699a95d0afee92372f263779 Mon Sep 17 00:00:00 2001 From: Carl Beekhuizen Date: Wed, 18 Mar 2020 20:08:36 +0100 Subject: [PATCH 03/12] Remove type-hint for backwards compatability with python 3.5 --- py_ecc/bls/hash_to_curve.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/py_ecc/bls/hash_to_curve.py b/py_ecc/bls/hash_to_curve.py index 5f697e02..5257d52b 100644 --- a/py_ecc/bls/hash_to_curve.py +++ b/py_ecc/bls/hash_to_curve.py @@ -42,7 +42,7 @@ def hash_to_field_FQ2(message: bytes, count: int, DST: bytes) -> List[FQ2]: M = 2 # m is the extension degree of FQ2 len_in_bytes = count * M * HASH_TO_FIELD_L pseudo_random_bytes = expand_message_xmd(message, DST, len_in_bytes) - u: List[FQ2] = [] + u = [] for i in range(0, count): e: List[int] = [] for j in range(0, M): From 3e38b18b3282007e38a8efb5203cd39673dbe54e Mon Sep 17 00:00:00 2001 From: Carl Beekhuizen Date: Tue, 24 Mar 2020 17:16:48 +0100 Subject: [PATCH 04/12] structs for I2OSP & py 3.5 compatability` --- py_ecc/bls/hash.py | 5 +++-- py_ecc/bls/hash_to_curve.py | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/py_ecc/bls/hash.py b/py_ecc/bls/hash.py index 38593e0f..076254db 100644 --- a/py_ecc/bls/hash.py +++ b/py_ecc/bls/hash.py @@ -1,5 +1,6 @@ import hmac import math +import struct from typing import Union from hashlib import sha256 as _sha256 @@ -54,12 +55,12 @@ def xor(a: bytes, b: bytes) -> bytes: def expand_message_xmd(msg: bytes, DST: bytes, len_in_bytes: int) -> bytes: ell = math.ceil(len_in_bytes / HASH_TO_FIELD_B_IN_BYTES) - DST_prime = len(DST).to_bytes(1, 'big') + DST + DST_prime = struct.pack('B', len(DST)) + DST # Prepend the length if the DST as a single byte Z_pad = b'\x00' * HASH_TO_FIELD_R_IN_BYTES l_i_b_str = len_in_bytes.to_bytes(2, 'big') b_0 = sha256(Z_pad + msg + l_i_b_str + b'\x00' + DST_prime) b = [sha256(b_0 + b'\x01' + DST_prime)] for i in range(2, ell + 1): - b.append(sha256(xor(b_0, b[i - 2]) + i.to_bytes(1, 'big') + DST_prime)) + b.append(sha256(xor(b_0, b[i - 2]) + struct.pack('B', i) + DST_prime)) pseudo_random_bytes = b''.join(b) return pseudo_random_bytes[:len_in_bytes] diff --git a/py_ecc/bls/hash_to_curve.py b/py_ecc/bls/hash_to_curve.py index 5257d52b..fd223a7e 100644 --- a/py_ecc/bls/hash_to_curve.py +++ b/py_ecc/bls/hash_to_curve.py @@ -44,7 +44,7 @@ def hash_to_field_FQ2(message: bytes, count: int, DST: bytes) -> List[FQ2]: pseudo_random_bytes = expand_message_xmd(message, DST, len_in_bytes) u = [] for i in range(0, count): - e: List[int] = [] + e = [] for j in range(0, M): elem_offset = HASH_TO_FIELD_L * (j + i * M) tv = pseudo_random_bytes[elem_offset: elem_offset + HASH_TO_FIELD_L] From d35650079d148d59853e82093d9bb0575ce056b1 Mon Sep 17 00:00:00 2001 From: Carl Beekhuizen Date: Thu, 26 Mar 2020 14:01:55 +0100 Subject: [PATCH 05/12] Update H2C comments -> V06 --- README.md | 2 +- py_ecc/bls/hash_to_curve.py | 14 ++++++++------ py_ecc/fields/optimized_field_elements.py | 4 ++-- py_ecc/optimized_bls12_381/constants.py | 2 +- 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 53e1a702..012164cd 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ pip install py_ecc ## BLS Signatures -`py_ecc` implements the [IETF BLS draft standard v0](https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-00) with [hash-to-curve v5](https://tools.ietf.org/html/draft-irtf-cfrg-hash-to-curve-05) as per the inter-blockchain standardization agreement. The BLS standards specify [different ciphersuites](https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-00#section-4.2) which each have different functionality to accommodate various use cases. The following ciphersuites are availible from this library: +`py_ecc` implements the [IETF BLS draft standard v0](https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-00) with [hash-to-curve v6](https://tools.ietf.org/html/draft-irtf-cfrg-hash-to-curve-06) as per the inter-blockchain standardization agreement. The BLS standards specify [different ciphersuites](https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-00#section-4.2) which each have different functionality to accommodate various use cases. The following ciphersuites are availible from this library: - `G2Basic` also known as `BLS_SIG_BLS12381G2-SHA256-SSWU-RO-_NUL_` - `G2MessageAugmentation` also known as `BLS_SIG_BLS12381G2-SHA256-SSWU-RO-_AUG_` diff --git a/py_ecc/bls/hash_to_curve.py b/py_ecc/bls/hash_to_curve.py index fd223a7e..daba28a0 100644 --- a/py_ecc/bls/hash_to_curve.py +++ b/py_ecc/bls/hash_to_curve.py @@ -19,10 +19,12 @@ def hash_to_G2(message: bytes, DST: bytes) -> 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 + https://tools.ietf.org/html/draft-irtf-cfrg-hash-to-curve-06#section-6.6.3 - 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 + The idea is to first hash into FQ2 and then use SSWU to map the result into G2. + + Contants and inputs follow the ciphersuite ``BLS12381G2_XMD:SHA-256_SSWU_RO_`` defined here: + https://tools.ietf.org/html/draft-irtf-cfrg-hash-to-curve-06#section-8.7.2 """ u0, u1 = hash_to_field_FQ2(message, 2, DST) q0 = map_to_curve_G2(u0) @@ -37,7 +39,7 @@ def hash_to_field_FQ2(message: bytes, count: int, DST: bytes) -> List[FQ2]: Hash To Base Field for FQ2 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 + https://tools.ietf.org/html/draft-irtf-cfrg-hash-to-curve-06#section-5.2 """ M = 2 # m is the extension degree of FQ2 len_in_bytes = count * M * HASH_TO_FIELD_L @@ -59,10 +61,10 @@ 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 + SWU Map: https://tools.ietf.org/html/draft-irtf-cfrg-hash-to-curve-06#section-6.6.3 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 + 3-Isogeny Map: https://tools.ietf.org/html/draft-irtf-cfrg-hash-to-curve-06#appendix-C.3 """ (x, y, z) = optimized_swu_G2(u) return iso_map_G2(x, y, z) diff --git a/py_ecc/fields/optimized_field_elements.py b/py_ecc/fields/optimized_field_elements.py index c1c4e691..23207e82 100644 --- a/py_ecc/fields/optimized_field_elements.py +++ b/py_ecc/fields/optimized_field_elements.py @@ -188,7 +188,7 @@ def sgn0_be(self: T_FQ) -> int: 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 + https://tools.ietf.org/html/draft-irtf-cfrg-hash-to-curve-06#section-4.1.1 """ if self.n == 0: return 1 @@ -384,7 +384,7 @@ def sgn0_be(self: T_FQP) -> int: 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 + https://tools.ietf.org/html/draft-irtf-cfrg-hash-to-curve-06#section-4.1.1 """ sign = 0 for x_i in reversed(self.coeffs): diff --git a/py_ecc/optimized_bls12_381/constants.py b/py_ecc/optimized_bls12_381/constants.py index 6072e6de..dd9adf3c 100644 --- a/py_ecc/optimized_bls12_381/constants.py +++ b/py_ecc/optimized_bls12_381/constants.py @@ -54,5 +54,5 @@ ISO_3_MAP_COEFFICIENTS = (ISO_3_X_NUMERATOR, ISO_3_X_DENOMINATOR, ISO_3_Y_NUMERATOR, ISO_3_Y_DENOMINATOR) # noqa: E501 -# h_eff from https://tools.ietf.org/html/draft-irtf-cfrg-hash-to-curve-05#section-8.9.2 +# h_eff from https://tools.ietf.org/html/draft-irtf-cfrg-hash-to-curve-06#section-8.7.2 H_EFF = 209869847837335686905080341498658477663839067235703451875306851526599783796572738804459333109033834234622528588876978987822447936461846631641690358257586228683615991308971558879306463436166481 # noqa: E501 From 1e545209b3116b19546e87b2422d1ffbb753d543 Mon Sep 17 00:00:00 2001 From: Carl Beekhuizen Date: Mon, 30 Mar 2020 20:24:21 +0200 Subject: [PATCH 06/12] Much faster single byte casting by using an array of ALL_BYTES --- py_ecc/bls/constants.py | 5 +++++ py_ecc/bls/hash.py | 6 +++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/py_ecc/bls/constants.py b/py_ecc/bls/constants.py index 03ab8b08..63370503 100644 --- a/py_ecc/bls/constants.py +++ b/py_ecc/bls/constants.py @@ -16,6 +16,11 @@ POW_2_382 = 2**382 POW_2_383 = 2**383 + +# Store all the possible single bytes for faster access in hash-to-field +ALL_BYTES = tuple(bytes([i]) for i in range(256)) + +# Paramaters for hashing to the field HASH_TO_FIELD_L = 64 HASH_TO_FIELD_B_IN_BYTES = 32 HASH_TO_FIELD_R_IN_BYTES = 64 diff --git a/py_ecc/bls/hash.py b/py_ecc/bls/hash.py index 076254db..d6e9c14c 100644 --- a/py_ecc/bls/hash.py +++ b/py_ecc/bls/hash.py @@ -1,10 +1,10 @@ import hmac import math -import struct from typing import Union from hashlib import sha256 as _sha256 from .constants import ( + ALL_BYTES, HASH_TO_FIELD_B_IN_BYTES, HASH_TO_FIELD_R_IN_BYTES, ) @@ -55,12 +55,12 @@ def xor(a: bytes, b: bytes) -> bytes: def expand_message_xmd(msg: bytes, DST: bytes, len_in_bytes: int) -> bytes: ell = math.ceil(len_in_bytes / HASH_TO_FIELD_B_IN_BYTES) - DST_prime = struct.pack('B', len(DST)) + DST # Prepend the length if the DST as a single byte + DST_prime = ALL_BYTES[len(DST)] + DST # Prepend the length if the DST as a single byte Z_pad = b'\x00' * HASH_TO_FIELD_R_IN_BYTES l_i_b_str = len_in_bytes.to_bytes(2, 'big') b_0 = sha256(Z_pad + msg + l_i_b_str + b'\x00' + DST_prime) b = [sha256(b_0 + b'\x01' + DST_prime)] for i in range(2, ell + 1): - b.append(sha256(xor(b_0, b[i - 2]) + struct.pack('B', i) + DST_prime)) + b.append(sha256(xor(b_0, b[i - 2]) + ALL_BYTES[i] + DST_prime)) pseudo_random_bytes = b''.join(b) return pseudo_random_bytes[:len_in_bytes] From 74cf8b75a0f2a2d41f3e4aab16467a31332d71ac Mon Sep 17 00:00:00 2001 From: Carl Beekhuizen Date: Mon, 6 Apr 2020 20:47:01 +0200 Subject: [PATCH 07/12] Move SHA into ciphersuites + @hwwhww's suggestions --- py_ecc/bls/ciphersuites.py | 24 +++++++++++++++--------- py_ecc/bls/constants.py | 3 ++- py_ecc/bls/hash.py | 26 ++++++++++++-------------- py_ecc/bls/hash_to_curve.py | 18 +++++++++++------- tests/bls/test_hash_to_curve.py | 19 ++++++++++++------- 5 files changed, 52 insertions(+), 38 deletions(-) diff --git a/py_ecc/bls/ciphersuites.py b/py_ecc/bls/ciphersuites.py index 466a5ba3..1d52de76 100644 --- a/py_ecc/bls/ciphersuites.py +++ b/py_ecc/bls/ciphersuites.py @@ -15,6 +15,7 @@ big_endian_to_int, ValidationError, ) +from hashlib import sha256 from py_ecc.fields import optimized_bls12_381_FQ12 as FQ12 from py_ecc.optimized_bls12_381 import ( @@ -45,6 +46,10 @@ class BaseG2Ciphersuite(abc.ABC): DST = b'' + @staticmethod + def xmd_hash_function(x: bytes) -> bytes: + return sha256(x).digest() + @staticmethod def PrivToPub(privkey: int) -> BLSPubkey: return G1_to_pubkey(multiply(G1, privkey)) @@ -65,14 +70,15 @@ def KeyValidate(PK: BLSPubkey) -> bool: return False return True - @staticmethod - def _CoreSign(SK: int, message: bytes, DST: bytes) -> BLSSignature: - message_point = hash_to_G2(message, DST) + @classmethod + def _CoreSign(cls, SK: int, message: bytes, DST: bytes) -> BLSSignature: + message_point = hash_to_G2(message, DST, cls.xmd_hash_function) signature_point = multiply(message_point, SK) return G2_to_signature(signature_point) - @staticmethod - def _CoreVerify(PK: BLSPubkey, message: bytes, signature: BLSSignature, DST: bytes) -> bool: + @classmethod + def _CoreVerify(cls, PK: BLSPubkey, message: bytes, + signature: BLSSignature, DST: bytes) -> bool: try: signature_point = signature_to_G2(signature) final_exponentiation = final_exponentiate( @@ -81,7 +87,7 @@ def _CoreVerify(PK: BLSPubkey, message: bytes, signature: BLSSignature, DST: byt G1, final_exponentiate=False, ) * pairing( - hash_to_G2(message, DST), + hash_to_G2(message, DST, cls.xmd_hash_function), neg(pubkey_to_G1(PK)), final_exponentiate=False, ) @@ -98,15 +104,15 @@ def Aggregate(signatures: Sequence[BLSSignature]) -> BLSSignature: accumulator = add(accumulator, signature_point) return G2_to_signature(accumulator) - @staticmethod - def _CoreAggregateVerify(pairs: Sequence[Tuple[BLSPubkey, bytes]], + @classmethod + def _CoreAggregateVerify(cls, pairs: Sequence[Tuple[BLSPubkey, bytes]], signature: BLSSignature, DST: bytes) -> bool: try: signature_point = signature_to_G2(signature) accumulator = FQ12.one() for pk, message in pairs: pubkey_point = pubkey_to_G1(pk) - message_point = hash_to_G2(message, DST) + message_point = hash_to_G2(message, DST, cls.xmd_hash_function) accumulator *= pairing(message_point, pubkey_point, final_exponentiate=False) accumulator *= pairing(signature_point, neg(G1), final_exponentiate=False) return final_exponentiate(accumulator) == FQ12.one() diff --git a/py_ecc/bls/constants.py b/py_ecc/bls/constants.py index 63370503..3fc74368 100644 --- a/py_ecc/bls/constants.py +++ b/py_ecc/bls/constants.py @@ -20,7 +20,8 @@ # Store all the possible single bytes for faster access in hash-to-field ALL_BYTES = tuple(bytes([i]) for i in range(256)) -# Paramaters for hashing to the field +# Paramaters for hashing to the field as specified in: +# https://tools.ietf.org/html/draft-irtf-cfrg-hash-to-curve-06#section-8.7 HASH_TO_FIELD_L = 64 HASH_TO_FIELD_B_IN_BYTES = 32 HASH_TO_FIELD_R_IN_BYTES = 64 diff --git a/py_ecc/bls/hash.py b/py_ecc/bls/hash.py index d6e9c14c..3fd27d09 100644 --- a/py_ecc/bls/hash.py +++ b/py_ecc/bls/hash.py @@ -1,7 +1,10 @@ import hmac import math -from typing import Union -from hashlib import sha256 as _sha256 +from typing import ( + Callable, + Union, +) +from hashlib import sha256 from .constants import ( ALL_BYTES, @@ -16,7 +19,7 @@ def hkdf_extract(salt: Union[bytes, bytearray], ikm: Union[bytes, bytearray]) -> https://tools.ietf.org/html/rfc5869 """ - return hmac.new(salt, ikm, _sha256).digest() + return hmac.new(salt, ikm, sha256).digest() def hkdf_expand(prk: Union[bytes, bytearray], info: Union[bytes, bytearray], length: int) -> bytes: @@ -36,31 +39,26 @@ def hkdf_expand(prk: Union[bytes, bytearray], info: Union[bytes, bytearray], len text = previous + info + bytes([i + 1]) # T(i + 1) = HMAC(T(i) || info || i) - previous = bytearray(hmac.new(prk, text, _sha256).digest()) + previous = bytearray(hmac.new(prk, text, sha256).digest()) okm.extend(previous) # Return first `length` bytes. return okm[:length] -def sha256(x: bytes) -> bytes: - m = _sha256() - m.update(x) - return m.digest() - - def xor(a: bytes, b: bytes) -> bytes: return bytes(_a ^ _b for _a, _b in zip(a, b)) -def expand_message_xmd(msg: bytes, DST: bytes, len_in_bytes: int) -> bytes: +def expand_message_xmd(msg: bytes, DST: bytes, len_in_bytes: int, + hash_function: Callable[[bytes], bytes]) -> bytes: ell = math.ceil(len_in_bytes / HASH_TO_FIELD_B_IN_BYTES) DST_prime = ALL_BYTES[len(DST)] + DST # Prepend the length if the DST as a single byte Z_pad = b'\x00' * HASH_TO_FIELD_R_IN_BYTES l_i_b_str = len_in_bytes.to_bytes(2, 'big') - b_0 = sha256(Z_pad + msg + l_i_b_str + b'\x00' + DST_prime) - b = [sha256(b_0 + b'\x01' + DST_prime)] + b_0 = hash_function(Z_pad + msg + l_i_b_str + b'\x00' + DST_prime) + b = [hash_function(b_0 + b'\x01' + DST_prime)] for i in range(2, ell + 1): - b.append(sha256(xor(b_0, b[i - 2]) + ALL_BYTES[i] + DST_prime)) + b.append(hash_function(xor(b_0, b[i - 2]) + ALL_BYTES[i] + DST_prime)) pseudo_random_bytes = b''.join(b) return pseudo_random_bytes[:len_in_bytes] diff --git a/py_ecc/bls/hash_to_curve.py b/py_ecc/bls/hash_to_curve.py index daba28a0..f5192adb 100644 --- a/py_ecc/bls/hash_to_curve.py +++ b/py_ecc/bls/hash_to_curve.py @@ -1,4 +1,7 @@ -from typing import List +from typing import ( + Callable, + Tuple, +) from py_ecc.fields import ( optimized_bls12_381_FQ2 as FQ2, ) @@ -16,7 +19,8 @@ # Hash to G2 -def hash_to_G2(message: bytes, DST: bytes) -> G2Uncompressed: +def hash_to_G2(message: bytes, DST: bytes, + hash_function: Callable[[bytes], bytes]) -> G2Uncompressed: """ Convert a message to a point on G2 as defined here: https://tools.ietf.org/html/draft-irtf-cfrg-hash-to-curve-06#section-6.6.3 @@ -26,7 +30,7 @@ def hash_to_G2(message: bytes, DST: bytes) -> G2Uncompressed: Contants and inputs follow the ciphersuite ``BLS12381G2_XMD:SHA-256_SSWU_RO_`` defined here: https://tools.ietf.org/html/draft-irtf-cfrg-hash-to-curve-06#section-8.7.2 """ - u0, u1 = hash_to_field_FQ2(message, 2, DST) + u0, u1 = hash_to_field_FQ2(message, 2, DST, hash_function) q0 = map_to_curve_G2(u0) q1 = map_to_curve_G2(u1) r = add(q0, q1) @@ -34,7 +38,8 @@ def hash_to_G2(message: bytes, DST: bytes) -> G2Uncompressed: return p -def hash_to_field_FQ2(message: bytes, count: int, DST: bytes) -> List[FQ2]: +def hash_to_field_FQ2(message: bytes, count: int, DST: bytes, + hash_function: Callable[[bytes], bytes]) -> Tuple[FQ2, ...]: """ Hash To Base Field for FQ2 @@ -43,7 +48,7 @@ def hash_to_field_FQ2(message: bytes, count: int, DST: bytes) -> List[FQ2]: """ M = 2 # m is the extension degree of FQ2 len_in_bytes = count * M * HASH_TO_FIELD_L - pseudo_random_bytes = expand_message_xmd(message, DST, len_in_bytes) + pseudo_random_bytes = expand_message_xmd(message, DST, len_in_bytes, hash_function) u = [] for i in range(0, count): e = [] @@ -52,8 +57,7 @@ def hash_to_field_FQ2(message: bytes, count: int, DST: bytes) -> List[FQ2]: tv = pseudo_random_bytes[elem_offset: elem_offset + HASH_TO_FIELD_L] e.append(int.from_bytes(tv, 'big') % field_modulus) u.append(FQ2(e)) - - return u + return tuple(u) def map_to_curve_G2(u: FQ2) -> G2Uncompressed: diff --git a/tests/bls/test_hash_to_curve.py b/tests/bls/test_hash_to_curve.py index 0efcad90..ca18abf9 100644 --- a/tests/bls/test_hash_to_curve.py +++ b/tests/bls/test_hash_to_curve.py @@ -6,6 +6,7 @@ big_endian_to_int, ) import pytest +from hashlib import sha256 from py_ecc.bls.hash_to_curve import hash_to_G2 from py_ecc.bls.constants import ( @@ -61,24 +62,28 @@ def test_iso_map_G2(iso_x, iso_y, iso_z, g2_x, g2_y): # Tests taken from: https://github.com/cfrg/draft-irtf-cfrg-hash-to-curve/blob/master/draft-irtf-cfrg-hash-to-curve.md#bls12381g2_xmdsha-256_sswu_ro_ @pytest.mark.parametrize( - 'msg,x,y', + 'msg,x,y,H', [ (b'', FQ2([0x0d3b02ee071b12d1e79138c3900ca3da7b8021ac462fe6ed68080dc9a5f1c5de46b7fe171e8b3e4e7537e7746757aeca, 0x0d4733459fead6a1f30e5f92df08ecfd0db9bcd0f3e2f2de0f00c8f45e081420aa4392eade61eade57d7a68474672fc1]), - FQ2([0x09cc6f7b3074f0c82510e65d8fc58f6033e03ba7358005a13e2bbd7f429b080f29731ef08c3780c9e3c746578b96b05c, 0x0011531b8e08900a4f6f612e1e27432961419ce6a5ee3ec904a53588982d36ec4ea37be80b6cb7d986b38faec67dbe44])), + FQ2([0x09cc6f7b3074f0c82510e65d8fc58f6033e03ba7358005a13e2bbd7f429b080f29731ef08c3780c9e3c746578b96b05c, 0x0011531b8e08900a4f6f612e1e27432961419ce6a5ee3ec904a53588982d36ec4ea37be80b6cb7d986b38faec67dbe44]), + lambda x: sha256(x).digest()), (b'abc', FQ2([0x0b6d276d0bfbddde617a9ab4c175b07c9c4aecad2cdd6cc9ca541b61334a69c58680ef5692bbad03d2f572838df32b66, 0x139e9d78ff6d9d163f979d14a64c5e57f82f1ef7e42ece338b571a9e92c0666f0f6bf1a5fc21e2d32bcb6432eab7037c]), - FQ2([0x022f9ee5d596d06c5f2f735c3c5f743978f79fd57bf7d4291e221227f490d3f276066de9f9edc89c57e048ef4cf0ef72, 0x14dd23517516a80d1d840e34f51dfb76946c7670fca0f36ad8ec9bde4ea82dfae119a21b076519bcc1c00152989a4d45])), + FQ2([0x022f9ee5d596d06c5f2f735c3c5f743978f79fd57bf7d4291e221227f490d3f276066de9f9edc89c57e048ef4cf0ef72, 0x14dd23517516a80d1d840e34f51dfb76946c7670fca0f36ad8ec9bde4ea82dfae119a21b076519bcc1c00152989a4d45]), + lambda x: sha256(x).digest()), (b'abcdef0123456789', FQ2([0x0ded52c30aace28d3e9cc5c1b47861ae4dd4e9cd17622e0f5b9d584af0397cd0e3bae80d4ee2d9d4b18c390f63154dfd, 0x046701a03f361a0b8392ca387585f7ee6534dcec9450a035e39dc37387d5ca079b9557447f7d9cad0bd9671cb65ada02]), - FQ2([0x07a5cf56c5ea1d69ad59c0e80cc16c0c1b27f02840b396eb0ea320f70e87f705c6fa70cfeb9719b14badbb058bec5a4c, 0x0674d1f7c9e8e84d8d7a07b40231257571c43160fd566e8d24459d17ca52f6068e1b63aaae5359d8869d4abc66de66b6])), + FQ2([0x07a5cf56c5ea1d69ad59c0e80cc16c0c1b27f02840b396eb0ea320f70e87f705c6fa70cfeb9719b14badbb058bec5a4c, 0x0674d1f7c9e8e84d8d7a07b40231257571c43160fd566e8d24459d17ca52f6068e1b63aaae5359d8869d4abc66de66b6]), + lambda x: sha256(x).digest()), (b'a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', FQ2([0x0161130ef4aa2f60f751e6b3dd48ac6e994d2d2613897c5dd26945bc72f33cc2977e1255c3f2dc0f1440d15a71c29b40, 0x06db1818f132a61f5fe86d315faa8de4653049ac9cf7fbbc6d9987e5864d82a0156259d56192109bafddd5c30b9f01f5]), - FQ2([0x00f7fab0fedc978b974a38a1755244727b8a4eb31073653fa949594645ad181880d20ff0c91c4375b7e451fe803c9847, 0x0964d550ee8752b6db99555ffcd442b4185267f31e3d57435ea73896a7a9fe952bd67f90fd75f4413212ac9640a7672c])), + FQ2([0x00f7fab0fedc978b974a38a1755244727b8a4eb31073653fa949594645ad181880d20ff0c91c4375b7e451fe803c9847, 0x0964d550ee8752b6db99555ffcd442b4185267f31e3d57435ea73896a7a9fe952bd67f90fd75f4413212ac9640a7672c]), + lambda x: sha256(x).digest()), ] ) -def test_hash_to_G2(msg, x, y): - point = hash_to_G2(msg, DST) +def test_hash_to_G2(msg, x, y, H): + point = hash_to_G2(msg, DST, H) assert is_on_curve(point, b2) # Affine From f9a6ad44d63a36e4de861e7ca10958bc2a0eb690 Mon Sep 17 00:00:00 2001 From: Carl Beekhuizen Date: Wed, 29 Apr 2020 20:55:15 +0200 Subject: [PATCH 08/12] H2C passes around "hashlib._hash" objects --- py_ecc/bls/ciphersuites.py | 5 +---- py_ecc/bls/constants.py | 2 -- py_ecc/bls/hash.py | 18 +++++++++--------- py_ecc/bls/hash_to_curve.py | 8 +++++--- tests/bls/test_hash_to_curve.py | 15 ++++++--------- 5 files changed, 21 insertions(+), 27 deletions(-) diff --git a/py_ecc/bls/ciphersuites.py b/py_ecc/bls/ciphersuites.py index 1d52de76..c5a0db91 100644 --- a/py_ecc/bls/ciphersuites.py +++ b/py_ecc/bls/ciphersuites.py @@ -45,10 +45,7 @@ class BaseG2Ciphersuite(abc.ABC): DST = b'' - - @staticmethod - def xmd_hash_function(x: bytes) -> bytes: - return sha256(x).digest() + xmd_hash_function = sha256 @staticmethod def PrivToPub(privkey: int) -> BLSPubkey: diff --git a/py_ecc/bls/constants.py b/py_ecc/bls/constants.py index 3fc74368..d2854d39 100644 --- a/py_ecc/bls/constants.py +++ b/py_ecc/bls/constants.py @@ -23,5 +23,3 @@ # Paramaters for hashing to the field as specified in: # https://tools.ietf.org/html/draft-irtf-cfrg-hash-to-curve-06#section-8.7 HASH_TO_FIELD_L = 64 -HASH_TO_FIELD_B_IN_BYTES = 32 -HASH_TO_FIELD_R_IN_BYTES = 64 diff --git a/py_ecc/bls/hash.py b/py_ecc/bls/hash.py index 3fd27d09..c38621d8 100644 --- a/py_ecc/bls/hash.py +++ b/py_ecc/bls/hash.py @@ -5,11 +5,10 @@ Union, ) from hashlib import sha256 +from _hashlib import HASH from .constants import ( ALL_BYTES, - HASH_TO_FIELD_B_IN_BYTES, - HASH_TO_FIELD_R_IN_BYTES, ) @@ -50,15 +49,16 @@ def xor(a: bytes, b: bytes) -> bytes: return bytes(_a ^ _b for _a, _b in zip(a, b)) -def expand_message_xmd(msg: bytes, DST: bytes, len_in_bytes: int, - hash_function: Callable[[bytes], bytes]) -> bytes: - ell = math.ceil(len_in_bytes / HASH_TO_FIELD_B_IN_BYTES) +def expand_message_xmd(msg: bytes, DST: bytes, len_in_bytes: int, hash_function: HASH) -> bytes: + b_in_bytes = hash_function().digest_size + r_in_bytes = hash_function().block_size + ell = math.ceil(len_in_bytes / b_in_bytes) DST_prime = ALL_BYTES[len(DST)] + DST # Prepend the length if the DST as a single byte - Z_pad = b'\x00' * HASH_TO_FIELD_R_IN_BYTES + Z_pad = b'\x00' * r_in_bytes l_i_b_str = len_in_bytes.to_bytes(2, 'big') - b_0 = hash_function(Z_pad + msg + l_i_b_str + b'\x00' + DST_prime) - b = [hash_function(b_0 + b'\x01' + DST_prime)] + b_0 = hash_function(Z_pad + msg + l_i_b_str + b'\x00' + DST_prime).digest() + b = [hash_function(b_0 + b'\x01' + DST_prime).digest()] for i in range(2, ell + 1): - b.append(hash_function(xor(b_0, b[i - 2]) + ALL_BYTES[i] + DST_prime)) + b.append(hash_function(xor(b_0, b[i - 2]) + ALL_BYTES[i] + DST_prime).digest()) pseudo_random_bytes = b''.join(b) return pseudo_random_bytes[:len_in_bytes] diff --git a/py_ecc/bls/hash_to_curve.py b/py_ecc/bls/hash_to_curve.py index f5192adb..5c097fb2 100644 --- a/py_ecc/bls/hash_to_curve.py +++ b/py_ecc/bls/hash_to_curve.py @@ -2,6 +2,8 @@ Callable, Tuple, ) +from _hashlib import HASH + from py_ecc.fields import ( optimized_bls12_381_FQ2 as FQ2, ) @@ -20,7 +22,7 @@ # Hash to G2 def hash_to_G2(message: bytes, DST: bytes, - hash_function: Callable[[bytes], bytes]) -> G2Uncompressed: + hash_function: HASH) -> G2Uncompressed: """ Convert a message to a point on G2 as defined here: https://tools.ietf.org/html/draft-irtf-cfrg-hash-to-curve-06#section-6.6.3 @@ -38,8 +40,8 @@ def hash_to_G2(message: bytes, DST: bytes, return p -def hash_to_field_FQ2(message: bytes, count: int, DST: bytes, - hash_function: Callable[[bytes], bytes]) -> Tuple[FQ2, ...]: +def hash_to_field_FQ2(message: bytes, count: int, + DST: bytes, hash_function: HASH) -> Tuple[FQ2, ...]: """ Hash To Base Field for FQ2 diff --git a/tests/bls/test_hash_to_curve.py b/tests/bls/test_hash_to_curve.py index ca18abf9..0b4c7dcb 100644 --- a/tests/bls/test_hash_to_curve.py +++ b/tests/bls/test_hash_to_curve.py @@ -61,25 +61,22 @@ def test_iso_map_G2(iso_x, iso_y, iso_z, g2_x, g2_y): # Tests taken from: https://github.com/cfrg/draft-irtf-cfrg-hash-to-curve/blob/master/draft-irtf-cfrg-hash-to-curve.md#bls12381g2_xmdsha-256_sswu_ro_ +@pytest.mark.parametrize('H', [sha256]) @pytest.mark.parametrize( - 'msg,x,y,H', + 'msg,x,y', [ (b'', FQ2([0x0d3b02ee071b12d1e79138c3900ca3da7b8021ac462fe6ed68080dc9a5f1c5de46b7fe171e8b3e4e7537e7746757aeca, 0x0d4733459fead6a1f30e5f92df08ecfd0db9bcd0f3e2f2de0f00c8f45e081420aa4392eade61eade57d7a68474672fc1]), - FQ2([0x09cc6f7b3074f0c82510e65d8fc58f6033e03ba7358005a13e2bbd7f429b080f29731ef08c3780c9e3c746578b96b05c, 0x0011531b8e08900a4f6f612e1e27432961419ce6a5ee3ec904a53588982d36ec4ea37be80b6cb7d986b38faec67dbe44]), - lambda x: sha256(x).digest()), + FQ2([0x09cc6f7b3074f0c82510e65d8fc58f6033e03ba7358005a13e2bbd7f429b080f29731ef08c3780c9e3c746578b96b05c, 0x0011531b8e08900a4f6f612e1e27432961419ce6a5ee3ec904a53588982d36ec4ea37be80b6cb7d986b38faec67dbe44])), (b'abc', FQ2([0x0b6d276d0bfbddde617a9ab4c175b07c9c4aecad2cdd6cc9ca541b61334a69c58680ef5692bbad03d2f572838df32b66, 0x139e9d78ff6d9d163f979d14a64c5e57f82f1ef7e42ece338b571a9e92c0666f0f6bf1a5fc21e2d32bcb6432eab7037c]), - FQ2([0x022f9ee5d596d06c5f2f735c3c5f743978f79fd57bf7d4291e221227f490d3f276066de9f9edc89c57e048ef4cf0ef72, 0x14dd23517516a80d1d840e34f51dfb76946c7670fca0f36ad8ec9bde4ea82dfae119a21b076519bcc1c00152989a4d45]), - lambda x: sha256(x).digest()), + FQ2([0x022f9ee5d596d06c5f2f735c3c5f743978f79fd57bf7d4291e221227f490d3f276066de9f9edc89c57e048ef4cf0ef72, 0x14dd23517516a80d1d840e34f51dfb76946c7670fca0f36ad8ec9bde4ea82dfae119a21b076519bcc1c00152989a4d45])), (b'abcdef0123456789', FQ2([0x0ded52c30aace28d3e9cc5c1b47861ae4dd4e9cd17622e0f5b9d584af0397cd0e3bae80d4ee2d9d4b18c390f63154dfd, 0x046701a03f361a0b8392ca387585f7ee6534dcec9450a035e39dc37387d5ca079b9557447f7d9cad0bd9671cb65ada02]), - FQ2([0x07a5cf56c5ea1d69ad59c0e80cc16c0c1b27f02840b396eb0ea320f70e87f705c6fa70cfeb9719b14badbb058bec5a4c, 0x0674d1f7c9e8e84d8d7a07b40231257571c43160fd566e8d24459d17ca52f6068e1b63aaae5359d8869d4abc66de66b6]), - lambda x: sha256(x).digest()), + FQ2([0x07a5cf56c5ea1d69ad59c0e80cc16c0c1b27f02840b396eb0ea320f70e87f705c6fa70cfeb9719b14badbb058bec5a4c, 0x0674d1f7c9e8e84d8d7a07b40231257571c43160fd566e8d24459d17ca52f6068e1b63aaae5359d8869d4abc66de66b6])), (b'a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', FQ2([0x0161130ef4aa2f60f751e6b3dd48ac6e994d2d2613897c5dd26945bc72f33cc2977e1255c3f2dc0f1440d15a71c29b40, 0x06db1818f132a61f5fe86d315faa8de4653049ac9cf7fbbc6d9987e5864d82a0156259d56192109bafddd5c30b9f01f5]), - FQ2([0x00f7fab0fedc978b974a38a1755244727b8a4eb31073653fa949594645ad181880d20ff0c91c4375b7e451fe803c9847, 0x0964d550ee8752b6db99555ffcd442b4185267f31e3d57435ea73896a7a9fe952bd67f90fd75f4413212ac9640a7672c]), - lambda x: sha256(x).digest()), + FQ2([0x00f7fab0fedc978b974a38a1755244727b8a4eb31073653fa949594645ad181880d20ff0c91c4375b7e451fe803c9847, 0x0964d550ee8752b6db99555ffcd442b4185267f31e3d57435ea73896a7a9fe952bd67f90fd75f4413212ac9640a7672c])), ] ) def test_hash_to_G2(msg, x, y, H): From f2cf04c84f2cfa23a7f8f675b0cecc961ab88964 Mon Sep 17 00:00:00 2001 From: Carl Beekhuizen Date: Wed, 29 Apr 2020 21:03:08 +0200 Subject: [PATCH 09/12] sanity checks for expand_message_xmd --- py_ecc/bls/hash.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/py_ecc/bls/hash.py b/py_ecc/bls/hash.py index c38621d8..fce039cf 100644 --- a/py_ecc/bls/hash.py +++ b/py_ecc/bls/hash.py @@ -52,7 +52,11 @@ def xor(a: bytes, b: bytes) -> bytes: def expand_message_xmd(msg: bytes, DST: bytes, len_in_bytes: int, hash_function: HASH) -> bytes: b_in_bytes = hash_function().digest_size r_in_bytes = hash_function().block_size + if len(DST) > 255: + raise ValueError('DST must be <= 255 bytes') ell = math.ceil(len_in_bytes / b_in_bytes) + if ell > 255: + raise ValueError('invalid len in bytes for hash function') DST_prime = ALL_BYTES[len(DST)] + DST # Prepend the length if the DST as a single byte Z_pad = b'\x00' * r_in_bytes l_i_b_str = len_in_bytes.to_bytes(2, 'big') From 8390e9122f6ec10452c7b3e41449c28f35883c3b Mon Sep 17 00:00:00 2001 From: Carl Beekhuizen Date: Wed, 29 Apr 2020 22:08:11 +0200 Subject: [PATCH 10/12] make mypy happy about `hashlib._hash` types --- py_ecc/bls/hash.py | 2 +- py_ecc/bls/hash_to_curve.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/py_ecc/bls/hash.py b/py_ecc/bls/hash.py index fce039cf..c2800bbb 100644 --- a/py_ecc/bls/hash.py +++ b/py_ecc/bls/hash.py @@ -5,7 +5,7 @@ Union, ) from hashlib import sha256 -from _hashlib import HASH +from _hashlib import HASH # type: ignore from .constants import ( ALL_BYTES, diff --git a/py_ecc/bls/hash_to_curve.py b/py_ecc/bls/hash_to_curve.py index 5c097fb2..ca620257 100644 --- a/py_ecc/bls/hash_to_curve.py +++ b/py_ecc/bls/hash_to_curve.py @@ -2,7 +2,7 @@ Callable, Tuple, ) -from _hashlib import HASH +from _hashlib import HASH # type: ignore from py_ecc.fields import ( optimized_bls12_381_FQ2 as FQ2, From 425112892b2ae82aa2c5b51279d0a586a9f81012 Mon Sep 17 00:00:00 2001 From: Carl Beekhuizen Date: Thu, 30 Apr 2020 10:28:45 +0200 Subject: [PATCH 11/12] Clean up unused imports --- py_ecc/bls/hash.py | 1 - py_ecc/bls/hash_to_curve.py | 1 - 2 files changed, 2 deletions(-) diff --git a/py_ecc/bls/hash.py b/py_ecc/bls/hash.py index c2800bbb..9760d707 100644 --- a/py_ecc/bls/hash.py +++ b/py_ecc/bls/hash.py @@ -1,7 +1,6 @@ import hmac import math from typing import ( - Callable, Union, ) from hashlib import sha256 diff --git a/py_ecc/bls/hash_to_curve.py b/py_ecc/bls/hash_to_curve.py index ca620257..eaf01756 100644 --- a/py_ecc/bls/hash_to_curve.py +++ b/py_ecc/bls/hash_to_curve.py @@ -1,5 +1,4 @@ from typing import ( - Callable, Tuple, ) from _hashlib import HASH # type: ignore From 39d8878471f987b4322dc209b56faf0b2043b0b2 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Tue, 5 May 2020 14:50:09 +0800 Subject: [PATCH 12/12] Remove unused 'type: ignore' comment --- py_ecc/bls/hash.py | 2 +- py_ecc/bls/hash_to_curve.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/py_ecc/bls/hash.py b/py_ecc/bls/hash.py index 9760d707..04b53261 100644 --- a/py_ecc/bls/hash.py +++ b/py_ecc/bls/hash.py @@ -4,7 +4,7 @@ Union, ) from hashlib import sha256 -from _hashlib import HASH # type: ignore +from _hashlib import HASH from .constants import ( ALL_BYTES, diff --git a/py_ecc/bls/hash_to_curve.py b/py_ecc/bls/hash_to_curve.py index eaf01756..8101fa0a 100644 --- a/py_ecc/bls/hash_to_curve.py +++ b/py_ecc/bls/hash_to_curve.py @@ -1,7 +1,7 @@ from typing import ( Tuple, ) -from _hashlib import HASH # type: ignore +from _hashlib import HASH from py_ecc.fields import ( optimized_bls12_381_FQ2 as FQ2,