From: Loup Vaillant Date: Sun, 20 Mar 2022 20:29:07 +0000 (+0100) Subject: Fixed Elligator reverse map comments X-Git-Url: https://git.codecow.com/?a=commitdiff_plain;h=303016ed6c54a00776a6853fb4d6667a302753b7;p=Monocypher.git Fixed Elligator reverse map comments Also took the opportunity to fix the Python inverse map when the point is zero (it used to fail, now it returns the all zero representative). --- diff --git a/src/monocypher.c b/src/monocypher.c index a58a691..1dd8f2e 100644 --- a/src/monocypher.c +++ b/src/monocypher.c @@ -2680,43 +2680,36 @@ void crypto_hidden_to_curve(uint8_t curve[32], const uint8_t hidden[32]) // Let sq = -non_square * u * (u+A) // if sq is not a square, or u = -A, there is no mapping // Assuming there is a mapping: -// if v is positive: r = sqrt(-(u+A) / u) -// if v is negative: r = sqrt(-u / (u+A)) +// if v is positive: r = sqrt(-u / (non_square * (u+A))) +// if v is negative: r = sqrt(-(u+A) / (non_square * u )) // // We compute isr = invsqrt(-non_square * u * (u+A)) -// if it wasn't a non-zero square, abort. +// if it wasn't a square, abort. // else, isr = sqrt(-1 / (non_square * u * (u+A)) // -// This causes us to abort if u is zero, even though we shouldn't. This -// never happens in practice, because (i) a random point in the curve has -// a negligible chance of being zero, and (ii) scalar multiplication with -// a trimmed scalar *never* yields zero. +// If v is positive, we return isr * u: +// isr * u = sqrt(-1 / (non_square * u * (u+A)) * u +// isr * u = sqrt(-u / (non_square * (u+A)) // -// Since: +// If v is negative, we return isr * (u+A): // isr * (u+A) = sqrt(-1 / (non_square * u * (u+A)) * (u+A) -// isr * (u+A) = sqrt(-(u+A) / (non_square * u * (u+A)) -// and: -// isr = u = sqrt(-1 / (non_square * u * (u+A)) * u -// isr = u = sqrt(-u / (non_square * u * (u+A)) -// Therefore: -// if v is positive: r = isr * (u+A) -// if v is negative: r = isr * u +// isr * (u+A) = sqrt(-(u+A) / (non_square * u) int crypto_curve_to_hidden(u8 hidden[32], const u8 public_key[32], u8 tweak) { fe t1, t2, t3; - fe_frombytes(t1, public_key, 1); + fe_frombytes(t1, public_key, 1); // t1 = u - fe_add(t2, t1, A); + fe_add(t2, t1, A); // t2 = u + A fe_mul(t3, t1, t2); fe_mul_small(t3, t3, -2); - int is_square = invsqrt(t3, t3); + int is_square = invsqrt(t3, t3); // t3 = sqrt(-1 / non_square * u * (u+A)) if (is_square) { // The only variable time bit. This ultimately reveals how many // tries it took us to find a representable key. // This does not affect security as long as we try keys at random. - fe_ccopy (t1, t2, tweak & 1); - fe_mul (t3, t1, t3); + fe_ccopy (t1, t2, tweak & 1); // multiply by u if v is positive, + fe_mul (t3, t1, t3); // multiply by u+A otherwise fe_mul_small(t1, t3, 2); fe_neg (t2, t3); fe_ccopy (t3, t2, fe_isodd(t1)); diff --git a/tests/gen/elligator-inverse.py b/tests/gen/elligator-inverse.py index d8c0cd0..f607ee4 100755 --- a/tests/gen/elligator-inverse.py +++ b/tests/gen/elligator-inverse.py @@ -55,6 +55,7 @@ from elligator import can_curve_to_hash from elligator import curve_to_hash from elligator import fast_curve_to_hash +from elligator import fe from elligator import hash_to_curve from elligator import print_raw @@ -63,16 +64,20 @@ from elligator_scalarmult import scalarmult from random import randrange from random import seed -def private_to_curve_and_hash(scalar, tweak): - cofactor = scalar % 8 +def redundant_curve_to_hash(u, tweak): v_is_negative = tweak % 2 == 1; - msb = (tweak // 2**6) * 2**254 - u = scalarmult(scalar, cofactor) r1 = None if can_curve_to_hash(u): r1 = curve_to_hash(u, v_is_negative) r2 = fast_curve_to_hash(u, v_is_negative) if r1 != r2: raise ValueError('Incoherent hash_to_curve') + return r1 + +def private_to_curve_and_hash(scalar, tweak): + cofactor = scalar % 8 + msb = (tweak // 2**6) * 2**254 + u = scalarmult(scalar, cofactor) + r1 = redundant_curve_to_hash(u, tweak) if r1 is None: return u, None if r1.val > 2**254: raise ValueError('Representative too big') @@ -84,6 +89,14 @@ def private_to_curve_and_hash(scalar, tweak): # not matter here since these are just tests. seed(12345) +# Zero +for tweak in range(2): + print_raw(0) + print(format(tweak, '02x') + ":") + print('00:') # Success + redundant_curve_to_hash(fe(0), tweak).print() + print() + # All possible failures for cofactor in range(8): tweak = randrange(0, 256) diff --git a/tests/gen/elligator.py b/tests/gen/elligator.py index 5092435..b1362bd 100644 --- a/tests/gen/elligator.py +++ b/tests/gen/elligator.py @@ -188,9 +188,9 @@ def curve_to_hash(u, v_is_negative): ##################### # Inverse square root. +# Returns (0 , True ) if x is zero. # Returns (sqrt(1/x) , True ) if x is non-zero square. # Returns (sqrt(sqrt(-1)/x), False) if x is not a square. -# Returns (0 , False) if x is zero. # We do not guarantee the sign of the square root. # # Notes: @@ -247,7 +247,7 @@ def invsqrt(x): quartic = x * isr**2 if quartic == fe(-1) or quartic == -sqrtm1: isr = isr * sqrtm1 - is_square = quartic == fe(1) or quartic == fe(-1) + is_square = quartic == fe(1) or quartic == fe(-1) or x == fe(0) return isr, is_square # From the paper: @@ -344,27 +344,20 @@ def fast_hash_to_curve(r): # Let sq = -non_square * u * (u+A) # if sq is not a square, or u = -A, there is no mapping # Assuming there is a mapping: -# if v is positive: r = sqrt(-(u+A) / u) -# if v is negative: r = sqrt(-u / (u+A)) +# if v is positive: r = sqrt(-u / (non_square * (u+A))) +# if v is negative: r = sqrt(-(u+A) / (non_square * u )) # # We compute isr = invsqrt(-non_square * u * (u+A)) -# if it wasn't a non-zero square, abort. +# if it wasn't a square, abort. # else, isr = sqrt(-1 / (non_square * u * (u+A)) # -# This causes us to abort if u is zero, even though we shouldn't. This -# never happens in practice, because (i) a random point in the curve has -# a negligible chance of being zero, and (ii) scalar multiplication with -# a trimmed scalar *never* yields zero. +# If v is positive, we return isr * u: +# isr * u = sqrt(-1 / (non_square * u * (u+A)) * u +# isr * u = sqrt(-u / (non_square * (u+A)) # -# Since: +# If v is negative, we return isr * (u+A): # isr * (u+A) = sqrt(-1 / (non_square * u * (u+A)) * (u+A) -# isr * (u+A) = sqrt(-(u+A) / (non_square * u * (u+A)) -# and: -# isr = u = sqrt(-1 / (non_square * u * (u+A)) * u -# isr = u = sqrt(-u / (non_square * u * (u+A)) -# Therefore: -# if v is positive: r = isr * (u+A) -# if v is negative: r = isr * u +# isr * (u+A) = sqrt(-(u+A) / (non_square * u) def fast_curve_to_hash(u, v_is_negative): t = u + A r = -non_square * u * t diff --git a/tests/gen/vectors/elligator_inv b/tests/gen/vectors/elligator_inv index 1948f97..05238a5 100644 --- a/tests/gen/vectors/elligator_inv +++ b/tests/gen/vectors/elligator_inv @@ -12,8 +12,3 @@ ff: 01: 00: 3cfb87c46c0b4575ca8175e0ed1c0ae9dae79db78df86997c4847b9f20b27718: - -0000000000000000000000000000000000000000000000000000000000000000: -00: -00: -0000000000000000000000000000000000000000000000000000000000000000: