I'm kind of torn about this change, because it adds a bunch of fairly complex code that's only needed for esoteric use cases, and it makes Elligator more complex, if mostly only for testing purposes. Basically, this is because Elligator is approximately ~8-to-1 when its domain is 56 bytes: 2 because it's [0..p+small] instead of [0..(p-1)/2], and 4 for cofactor removal. So when you call the inverse on a point, you need to say which inverse you want, i.e. a "hint". Of course, the inverse fails with probability 1/2. To make round-tripping a possibility (I'm not sure why you'd need this), the Elligator functions now return an unsigned char hint. This means that you can call Elligator, and then invert it with the hint you gave, and get the same buffer back out. This adds a bunch of complexity to Elligator, which didn't previously need to compute hints. The hinting is reasonably well tested, but it is known not to work for inputs which are very "large", i.e. end ~28 0xFF's (FIXME. Or roll back hinting...). There's also a significant chance that I'll revise the hinting mechanism. Create functions: decaf_448_invert_elligator_nonuniform decaf_448_invert_elligator_uniform decaf::Ed448::Point::invert_elligator decaf::Ed448::Point::steg_encode for inverting Elligator. This last one encodes to Point::STEG_BYTES = 64 bytes in a way which is supposed to be indistinguishable from random, so long as your point is random on the curve. Inverting Elligator costs about 2 square roots for nonuniform. For uniform, it's just Elligator -> diff -> invert, so it's 3 square roots. Stegging fails about half the time, and so costs about twice that, but the benchmark underreports it because it ignores outliers. The code is tested, but I haven't checked over the indistinguishability from random (I've only proved it correct...). There could well be a way to break the steg even without taking advantage of "very large" inputs or similar.master
@@ -472,7 +472,7 @@ void decaf_448_base_double_scalarmul_non_secret ( | |||
/** | |||
* @brief Test that a point is valid, for debugging purposes. | |||
* | |||
* @param [in] toTest The number to test. | |||
* @param [in] toTest The point to test. | |||
* @retval DECAF_TRUE The point is valid. | |||
* @retval DECAF_FALSE The point is invalid. | |||
*/ | |||
@@ -480,6 +480,17 @@ decaf_bool_t decaf_448_point_valid ( | |||
const decaf_448_point_t toTest | |||
) API_VIS WARN_UNUSED NONNULL1 NOINLINE; | |||
/** | |||
* @brief 2-torque a point, for debugging purposes. | |||
* | |||
* @param [out] q The point to torque. | |||
* @param [in] p The point to torque. | |||
*/ | |||
void decaf_448_point_debugging_2torque ( | |||
decaf_448_point_t q, | |||
const decaf_448_point_t p | |||
) API_VIS NONNULL2 NOINLINE; | |||
/** | |||
* @brief Almost-Elligator-like hash to curve. | |||
* | |||
@@ -495,6 +506,8 @@ decaf_bool_t decaf_448_point_valid ( | |||
* A factor of 2 due to the isogeny. | |||
* A factor of 2 because we quotient out the 2-torsion. | |||
* | |||
* This makes it about 8:1 overall. | |||
* | |||
* Negating the input (mod q) results in the same point. Inverting the input | |||
* (mod q) results in the negative point. This is the same as Elligator. | |||
* | |||
@@ -505,12 +518,67 @@ decaf_bool_t decaf_448_point_valid ( | |||
* | |||
* @param [in] hashed_data Output of some hash function. | |||
* @param [out] pt The data hashed to the curve. | |||
* @return A "hint" value which can be used to help invert the encoding. | |||
*/ | |||
void decaf_448_point_from_hash_nonuniform ( | |||
unsigned char | |||
decaf_448_point_from_hash_nonuniform ( | |||
decaf_448_point_t pt, | |||
const unsigned char hashed_data[DECAF_448_SER_BYTES] | |||
) API_VIS NONNULL2 NOINLINE; | |||
/** | |||
* @brief Inverse of elligator-like hash to curve. | |||
* | |||
* This function writes to the buffer, to make it so that | |||
* decaf_448_point_from_hash_nonuniform(buffer) = pt,hint | |||
* if possible. | |||
* | |||
* @param [out] recovered_hash Encoded data. | |||
* @param [in] pt The point to encode. | |||
* @param [in] hint The hint value returned from | |||
* decaf_448_point_from_hash_nonuniform. | |||
* | |||
* @retval DECAF_SUCCESS The inverse succeeded. | |||
* @retval DECAF_FAILURE The pt isn't the image of | |||
* decaf_448_point_from_hash_nonuniform with the given hint. | |||
* | |||
* @warning The hinting system is subject to change, especially in corner cases. | |||
* @warning FIXME The hinting system doesn't work for certain inputs which have many 0xFF. | |||
*/ | |||
decaf_bool_t | |||
decaf_448_invert_elligator_nonuniform ( | |||
unsigned char recovered_hash[DECAF_448_SER_BYTES], | |||
const decaf_448_point_t pt, | |||
unsigned char hint | |||
) API_VIS NONNULL2 NOINLINE WARN_UNUSED; | |||
/** | |||
* @brief Inverse of elligator-like hash to curve, uniform. | |||
* | |||
* This function modifies the first DECAF_448_SER_BYTES of the | |||
* buffer, to make it so that | |||
* decaf_448_point_from_hash_uniform(buffer) = pt,hint | |||
* if possible. | |||
* | |||
* @param [out] recovered_hash Encoded data. | |||
* @param [in] pt The point to encode. | |||
* @param [in] hint The hint value returned from | |||
* decaf_448_point_from_hash_nonuniform. | |||
* | |||
* @retval DECAF_SUCCESS The inverse succeeded. | |||
* @retval DECAF_FAILURE The pt isn't the image of | |||
* decaf_448_point_from_hash_uniform with the given hint. | |||
* | |||
* @warning The hinting system is subject to change, especially in corner cases. | |||
* @warning FIXME The hinting system doesn't work for certain inputs which have many 0xFF. | |||
*/ | |||
decaf_bool_t | |||
decaf_448_invert_elligator_uniform ( | |||
unsigned char recovered_hash[2*DECAF_448_SER_BYTES], | |||
const decaf_448_point_t pt, | |||
unsigned char hint | |||
) API_VIS NONNULL2 NOINLINE WARN_UNUSED; | |||
/** | |||
* @brief Indifferentiable hash function encoding to curve. | |||
* | |||
@@ -518,8 +586,9 @@ void decaf_448_point_from_hash_nonuniform ( | |||
* | |||
* @param [in] hashed_data Output of some hash function. | |||
* @param [out] pt The data hashed to the curve. | |||
* @return A "hint" value which can be used to help invert the encoding. | |||
*/ | |||
void decaf_448_point_from_hash_uniform ( | |||
unsigned char decaf_448_point_from_hash_uniform ( | |||
decaf_448_point_t pt, | |||
const unsigned char hashed_data[2*DECAF_448_SER_BYTES] | |||
) API_VIS NONNULL2 NOINLINE; | |||
@@ -532,6 +601,15 @@ void decaf_bzero ( | |||
size_t size | |||
) NONNULL1 API_VIS NOINLINE; | |||
/** | |||
* @brief Compare two buffers, returning DECAF_TRUE if they are equal. | |||
*/ | |||
decaf_bool_t decaf_memeq ( | |||
const void *data1, | |||
const void *data2, | |||
size_t size | |||
) NONNULL2 WARN_UNUSED API_VIS NOINLINE; | |||
/** | |||
* @brief Overwrite scalar with zeros. | |||
*/ | |||
@@ -386,6 +386,9 @@ public: | |||
/** @brief Size of a serialized element */ | |||
static const size_t SER_BYTES = DECAF_448_SER_BYTES; | |||
/** @brief Size of a stegged element */ | |||
static const size_t STEG_BYTES = DECAF_448_SER_BYTES + 8; | |||
/** @brief Bytes required for hash */ | |||
static const size_t HASH_BYTES = DECAF_448_SER_BYTES; | |||
@@ -476,19 +479,19 @@ public: | |||
* If the buffer is shorter than 2*HASH_BYTES, well, it won't be as uniform, | |||
* but the buffer will be zero-padded on the right. | |||
*/ | |||
inline void set_to_hash( const Block &s ) NOEXCEPT { | |||
inline unsigned char set_to_hash( const Block &s ) NOEXCEPT { | |||
if (s.size() < HASH_BYTES) { | |||
SecureBuffer b(HASH_BYTES); | |||
memcpy(b.data(), s.data(), s.size()); | |||
decaf_448_point_from_hash_nonuniform(p,b); | |||
return decaf_448_point_from_hash_nonuniform(p,b); | |||
} else if (s.size() == HASH_BYTES) { | |||
decaf_448_point_from_hash_nonuniform(p,s); | |||
return decaf_448_point_from_hash_nonuniform(p,s); | |||
} else if (s.size() < 2*HASH_BYTES) { | |||
SecureBuffer b(2*HASH_BYTES); | |||
memcpy(b.data(), s.data(), s.size()); | |||
decaf_448_point_from_hash_uniform(p,b); | |||
return decaf_448_point_from_hash_uniform(p,b); | |||
} else { | |||
decaf_448_point_from_hash_uniform(p,s); | |||
return decaf_448_point_from_hash_uniform(p,s); | |||
} | |||
} | |||
@@ -576,6 +579,34 @@ public: | |||
Point r((NOINIT())); decaf_448_base_double_scalarmul_non_secret(r.p,s_base.s,p,s.s); return r; | |||
} | |||
inline Point& debugging_torque_in_place() { | |||
decaf_448_point_debugging_2torque(p,p); | |||
return *this; | |||
} | |||
inline bool invert_elligator ( | |||
Buffer &buf, unsigned char hint | |||
) const NOEXCEPT { | |||
unsigned char buf2[2*HASH_BYTES]; | |||
memset(buf2,0,sizeof(buf2)); | |||
memcpy(buf2,buf,(buf.size() > 2*HASH_BYTES) ? 2*HASH_BYTES : buf.size()); | |||
decaf_bool_t ret; | |||
if (buf.size() > HASH_BYTES) { | |||
ret = decaf_448_invert_elligator_uniform(buf2, p, hint); | |||
} else { | |||
ret = decaf_448_invert_elligator_nonuniform(buf2, p, hint); | |||
} | |||
if (buf.size() < HASH_BYTES) { | |||
ret &= decaf_memeq(&buf2[buf.size()], &buf2[HASH_BYTES], HASH_BYTES - buf.size()); | |||
} | |||
memcpy(buf,buf2,(buf.size() < HASH_BYTES) ? buf.size() : HASH_BYTES); | |||
decaf_bzero(buf2,sizeof(buf2)); | |||
return !!ret; | |||
} | |||
/** @brief Steganographically encode this */ | |||
inline SecureBuffer steg_encode(SpongeRng &rng) const NOEXCEPT; | |||
/** @brief Return the base point */ | |||
static inline const Point base() NOEXCEPT { return Point(decaf_448_point_base); } | |||
@@ -201,6 +201,17 @@ inline Ed448::Point::Point(SpongeRng &rng, bool uniform) NOEXCEPT { | |||
rng.read(buffer); | |||
set_to_hash(buffer); | |||
} | |||
inline SecureBuffer Ed448::Point::steg_encode(SpongeRng &rng) const NOEXCEPT { | |||
SecureBuffer out(STEG_BYTES); | |||
bool done; | |||
do { | |||
rng.read(out.slice(HASH_BYTES-1,STEG_BYTES-HASH_BYTES+1)); | |||
done = invert_elligator(out, out[HASH_BYTES-1] & 7); /* 7 is kind of MAGIC */ | |||
} while (!done); | |||
return out; | |||
} | |||
/**@endcond*/ | |||
class Strobe : private KeccakSponge { | |||
@@ -29,8 +29,6 @@ typedef int64_t decaf_sdword_t; | |||
#error "Only supporting 32- and 64-bit platforms right now" | |||
#endif | |||
static const int QUADRATIC_NONRESIDUE = -1; | |||
#define sv static void | |||
#define snv static void __attribute__((noinline)) | |||
#define siv static inline void __attribute__((always_inline)) | |||
@@ -736,53 +734,83 @@ decaf_bool_t decaf_448_point_eq ( const decaf_448_point_t p, const decaf_448_poi | |||
return gf_eq(a,b); | |||
} | |||
void decaf_448_point_from_hash_nonuniform ( | |||
/** Inverse square root using addition chain. */ | |||
static decaf_bool_t gf_isqrt_chk(gf y, const gf x, decaf_bool_t allow_zero) { | |||
gf tmp0, tmp1; | |||
gf_isqrt(y,x); | |||
gf_sqr(tmp0,y); | |||
gf_mul(tmp1,tmp0,x); | |||
return gf_eq(tmp1,ONE) | (allow_zero & gf_eq(tmp1,ZERO)); | |||
} | |||
unsigned char decaf_448_point_from_hash_nonuniform ( | |||
decaf_448_point_t p, | |||
const unsigned char ser[DECAF_448_SER_BYTES] | |||
) { | |||
gf r0,r,a,b,c,dee,D,N,e; | |||
(void)gf_deser(r0,ser); | |||
gf r0,r,a,b,c,dee,D,N,rN,e; | |||
decaf_bool_t over = ~gf_deser(r0,ser); | |||
decaf_bool_t sgn_r0 = hibit(r0); | |||
gf_canon(r0); | |||
gf_sqr(a,r0); | |||
gf_mlw(r,a,QUADRATIC_NONRESIDUE); | |||
gf_sub(r,ZERO,a); /*gf_mlw(r,a,QUADRATIC_NONRESIDUE);*/ | |||
gf_mlw(dee,ONE,EDWARDS_D); | |||
gf_mlw(c,r,EDWARDS_D); | |||
/* Compute D := (dr+a-d)(dr-ar-d) with a=1 */ | |||
gf_sub(a,c,dee); | |||
gf_add(a,a,ONE); | |||
gf_sub(b, c, r); | |||
gf_sub(b, b, dee); | |||
decaf_bool_t special_identity_case = gf_eq(a,ZERO); | |||
gf_sub(b,c,r); | |||
gf_sub(b,b,dee); | |||
gf_mul(D,a,b); | |||
/* compute N := (r+1)(a-2d) with a=1 */ | |||
/* compute N := (r+1)(a-2d) */ | |||
gf_add(a,r,ONE); | |||
gf_mlw(N,a,1-2*EDWARDS_D); | |||
/* e = +-1/sqrt(+-ND) */ | |||
gf_mul(a,N,D); | |||
gf_isqrt(e,a); | |||
gf_sqr(b,e); | |||
gf_mul(c,a,b); | |||
decaf_bool_t square = gf_eq(c,ONE); | |||
gf_mul(rN,r,N); | |||
gf_mul(a,rN,D); | |||
/* *r0 if not square */ | |||
gf_mul(a,e,r0); | |||
cond_sel(e,a,e,square); | |||
cond_neg(e,hibit(e)^~square); | |||
decaf_bool_t square = gf_isqrt_chk(e,a,DECAF_FALSE); | |||
decaf_bool_t r_is_zero = gf_eq(r,ZERO); | |||
square |= r_is_zero; | |||
square |= special_identity_case; | |||
/* b <- t */ | |||
gf_mlw(a,e,1-2*EDWARDS_D); | |||
gf_sqr(b,a); | |||
gf_mul(a,b,N); | |||
/* b <- t/s */ | |||
cond_sel(c,r0,r,square); /* r? = sqr ? r : 1 */ | |||
/* In two steps to avoid overflow on 32-bit arch */ | |||
gf_mlw(a,c,1-2*EDWARDS_D); | |||
gf_mlw(b,a,1-2*EDWARDS_D); | |||
gf_sub(c,r,ONE); | |||
gf_mul(b,c,a); | |||
cond_neg(b,square); | |||
gf_sub(b,b,ONE); | |||
/* a <- s */ | |||
gf_mul(a,e,N); | |||
gf_mul(a,b,c); /* = r? * (r-1) * (a-2d)^2 with a=1 */ | |||
gf_mul(b,a,e); | |||
cond_neg(b,~square); | |||
cond_sel(c,r0,ONE,square); | |||
gf_mul(a,e,c); | |||
gf_mul(c,a,D); /* 1/s except for sign. FUTURE: simplify using this. */ | |||
gf_sub(b,b,c); | |||
/* a <- s = e * N * (sqr ? r : r0) | |||
* e^2 r N D = 1 | |||
* 1/s = 1/(e * N * (sqr ? r : r0)) = e * D * (sqr ? 1 : r0) | |||
*/ | |||
gf_mul(a,N,r0); | |||
cond_sel(rN,a,rN,square); | |||
gf_mul(a,rN,e); | |||
gf_mul(c,a,b); | |||
/* Normalize/negate */ | |||
decaf_bool_t neg_s = hibit(a)^~square; | |||
cond_neg(a,neg_s); /* ends up negative if ~square */ | |||
decaf_bool_t sgn_t_over_s = hibit(b)^neg_s; | |||
sgn_t_over_s &= ~gf_eq(N,ZERO); | |||
sgn_t_over_s |= gf_eq(D,ZERO); | |||
/* b <- t */ | |||
cond_sel(b,c,ONE,gf_eq(c,ZERO)); /* 0,0 -> 1,0 */ | |||
/* isogenize */ | |||
gf_sqr(c,a); /* s^2 */ | |||
gf_add(a,a,a); /* 2s */ | |||
@@ -792,16 +820,109 @@ void decaf_448_point_from_hash_nonuniform ( | |||
gf_sub(a,ONE,c); | |||
gf_mul(p->y,e,a); /* (1+s^2)(1-s^2) */ | |||
gf_mul(p->z,a,b); /* (1-s^2)t */ | |||
return (~square & 1) | (sgn_t_over_s & 2) | (sgn_r0 & 4) | (over & 8); | |||
} | |||
void decaf_448_point_from_hash_uniform ( | |||
/* TODO: source these impls instead of copy-pasting them */ | |||
decaf_bool_t | |||
decaf_448_invert_elligator_nonuniform ( | |||
unsigned char recovered_hash[DECAF_448_SER_BYTES], | |||
const decaf_448_point_t p, | |||
unsigned char hint | |||
) { | |||
decaf_bool_t sgn_s = -(hint & 1), | |||
sgn_t_over_s = -(hint>>1 & 1), | |||
sgn_r0 = -(hint>>2 & 1); | |||
gf a, b, c, d; | |||
gf_mlw ( a, p->y, 1-EDWARDS_D ); | |||
gf_mul ( c, a, p->t ); | |||
gf_mul ( a, p->x, p->z ); | |||
gf_sub ( d, c, a ); | |||
gf_add ( a, p->z, p->y ); | |||
gf_sub ( b, p->z, p->y ); | |||
gf_mul ( c, b, a ); | |||
gf_mlw ( b, c, -EDWARDS_D ); | |||
gf_isqrt ( a, b ); | |||
gf_mlw ( b, a, -EDWARDS_D ); | |||
gf_mul ( c, b, a ); | |||
gf_mul ( a, c, d ); | |||
gf_add ( d, b, b ); | |||
gf_mul ( c, d, p->z ); | |||
cond_neg ( b, sgn_t_over_s^~hibit(c) ); | |||
cond_neg ( c, sgn_t_over_s^~hibit(c) ); | |||
gf_mul ( d, b, p->y ); | |||
gf_add ( a, a, d ); | |||
cond_neg( a, hibit(a)^sgn_s); | |||
/* ok, s = a; c = -t/s */ | |||
gf_mul(b,c,a); | |||
gf_sub(b,ONE,b); /* t+1 */ | |||
gf_sqr(c,a); /* s^2 */ | |||
{ /* identity adjustments */ | |||
/* in case of identity, currently c=0, t=0, b=1, will encode to 1 */ | |||
/* if hint is 0, -> 0 */ | |||
/* if hint is to neg t/s, then go to infinity, effectively set s to 1 */ | |||
decaf_bool_t is_identity = gf_eq(p->x,ZERO); | |||
cond_sel(c,c,ONE,is_identity & sgn_t_over_s); | |||
cond_sel(b,b,ZERO,is_identity & ~sgn_t_over_s & ~sgn_s); /* identity adjust */ | |||
} | |||
gf_mlw(d,c,2*EDWARDS_D-1); /* $d = (2d-a)s^2 */ | |||
gf_add(a,b,d); /* num? */ | |||
gf_sub(d,b,d); /* den? */ | |||
gf_mul(b,a,d); /* n*d */ | |||
cond_sel(a,d,a,sgn_s); | |||
decaf_bool_t succ = gf_isqrt_chk(c,b,DECAF_TRUE); | |||
gf_mul(b,a,c); | |||
cond_neg(b, sgn_r0^hibit(b)); | |||
succ &= ~(gf_eq(b,ZERO) & sgn_r0); | |||
gf_canon(b); | |||
int k=0, bits=0; | |||
decaf_dword_t buf=0; | |||
FOR_LIMB(i, { | |||
buf |= (decaf_dword_t)b->limb[i]<<bits; | |||
for (bits += LBITS; (bits>=8 || i==DECAF_448_LIMBS-1) && k<DECAF_448_SER_BYTES; bits-=8, buf>>=8) { | |||
recovered_hash[k++]=buf; | |||
} | |||
}); | |||
return succ; | |||
} | |||
void decaf_448_point_debugging_2torque ( | |||
decaf_448_point_t q, | |||
const decaf_448_point_t p | |||
) { | |||
gf_sub(q->x,ZERO,p->x); | |||
gf_sub(q->y,ZERO,p->y); | |||
gf_cpy(q->z,p->z); | |||
gf_cpy(q->t,p->t); | |||
} | |||
unsigned char decaf_448_point_from_hash_uniform ( | |||
decaf_448_point_t pt, | |||
const unsigned char hashed_data[2*DECAF_448_SER_BYTES] | |||
) { | |||
decaf_448_point_t pt2; | |||
decaf_448_point_from_hash_nonuniform(pt,hashed_data); | |||
decaf_448_point_from_hash_nonuniform(pt2,&hashed_data[DECAF_448_SER_BYTES]); | |||
unsigned char ret1 = | |||
decaf_448_point_from_hash_nonuniform(pt,hashed_data); | |||
unsigned char ret2 = | |||
decaf_448_point_from_hash_nonuniform(pt2,&hashed_data[DECAF_448_SER_BYTES]); | |||
decaf_448_point_add(pt,pt,pt2); | |||
return ret1 | (ret2<<4); | |||
} | |||
decaf_bool_t decaf_448_invert_elligator_uniform ( | |||
unsigned char partial_hash[2*DECAF_448_SER_BYTES], | |||
const decaf_448_point_t p, | |||
unsigned char hint | |||
) { | |||
decaf_448_point_t pt2; | |||
decaf_448_point_from_hash_nonuniform(pt2,&partial_hash[DECAF_448_SER_BYTES]); | |||
decaf_448_point_sub(pt2,p,pt2); | |||
return decaf_448_invert_elligator_nonuniform(partial_hash,pt2,hint); | |||
} | |||
decaf_bool_t decaf_448_point_valid ( | |||
@@ -868,6 +989,20 @@ void decaf_448_point_destroy ( | |||
decaf_bzero(point, sizeof(decaf_448_point_t)); | |||
} | |||
decaf_bool_t decaf_memeq ( | |||
const void *data1_, | |||
const void *data2_, | |||
size_t size | |||
) { | |||
const unsigned char *data1 = (const unsigned char *)data1_; | |||
const unsigned char *data2 = (const unsigned char *)data2_; | |||
unsigned char ret = 0; | |||
for (; size; size--, data1++, data2++) { | |||
ret |= *data1 ^ *data2; | |||
} | |||
return (((decaf_dword_t)ret) - 1) >> 8; | |||
} | |||
void decaf_448_precomputed_destroy ( | |||
decaf_448_precomputed_s *pre | |||
) { | |||
@@ -450,7 +450,7 @@ void API_NS(point_encode)( unsigned char ser[SER_BYTES], const point_t p ) { | |||
gf_mlw ( a, p->y, 1-EDWARDS_D ); | |||
gf_mul ( c, a, p->t ); | |||
gf_mul ( a, p->x, p->z ); | |||
gf_sub ( d, c, a ); | |||
gf_sub ( d, c, a ); | |||
gf_add ( a, p->z, p->y ); | |||
gf_sub ( b, p->z, p->y ); | |||
gf_mul ( c, b, a ); | |||
@@ -985,22 +985,23 @@ decaf_bool_t API_NS(point_eq) ( const point_t p, const point_t q ) { | |||
return gf_eq(a,b); | |||
} | |||
void API_NS(point_from_hash_nonuniform) ( | |||
unsigned char API_NS(point_from_hash_nonuniform) ( | |||
point_t p, | |||
const unsigned char ser[SER_BYTES] | |||
) { | |||
gf r0,r,a,b,c,dee,D,N,e; | |||
(void)gf_deser(r0,ser); | |||
gf r0,r,a,b,c,dee,D,N,rN,e; | |||
decaf_bool_t over = ~gf_deser(r0,ser); | |||
decaf_bool_t sgn_r0 = hibit(r0); | |||
gf_canon(r0); | |||
gf_sqr(a,r0); | |||
/*gf_mlw(r,a,QUADRATIC_NONRESIDUE);*/ | |||
gf_sub(r,ZERO,a); | |||
gf_sub(r,ZERO,a); /*gf_mlw(r,a,QUADRATIC_NONRESIDUE);*/ | |||
gf_mlw(dee,ONE,EDWARDS_D); | |||
gf_mlw(c,r,EDWARDS_D); | |||
/* Compute D := (dr+a-d)(dr-ar-d) with a=1 */ | |||
gf_sub(a,c,dee); | |||
gf_add(a,a,ONE); | |||
decaf_bool_t special_identity_case = gf_eq(a,ZERO); | |||
gf_sub(b,c,r); | |||
gf_sub(b,b,dee); | |||
gf_mul(D,a,b); | |||
@@ -1010,26 +1011,47 @@ void API_NS(point_from_hash_nonuniform) ( | |||
gf_mlw(N,a,1-2*EDWARDS_D); | |||
/* e = +-1/sqrt(+-ND) */ | |||
gf_mul(a,N,D); | |||
decaf_bool_t square = gf_isqrt_chk(e,a,DECAF_FALSE); | |||
gf_mul(rN,r,N); | |||
gf_mul(a,rN,D); | |||
/* *r0 if not square */ | |||
gf_mul(a,e,r0); | |||
cond_sel(e,a,e,square); | |||
cond_neg(e,hibit(e)^~square); | |||
decaf_bool_t square = gf_isqrt_chk(e,a,DECAF_FALSE); | |||
decaf_bool_t r_is_zero = gf_eq(r,ZERO); | |||
square |= r_is_zero; | |||
square |= special_identity_case; | |||
/* b <- t */ | |||
gf_mlw(a,e,1-2*EDWARDS_D); | |||
gf_sqr(b,a); | |||
gf_mul(a,b,N); | |||
/* b <- t/s */ | |||
cond_sel(c,r0,r,square); /* r? = sqr ? r : 1 */ | |||
/* In two steps to avoid overflow on 32-bit arch */ | |||
gf_mlw(a,c,1-2*EDWARDS_D); | |||
gf_mlw(b,a,1-2*EDWARDS_D); | |||
gf_sub(c,r,ONE); | |||
gf_mul(b,c,a); | |||
cond_neg(b,square); | |||
gf_sub(b,b,ONE); | |||
gf_mul(a,b,c); /* = r? * (r-1) * (a-2d)^2 with a=1 */ | |||
gf_mul(b,a,e); | |||
cond_neg(b,~square); | |||
cond_sel(c,r0,ONE,square); | |||
gf_mul(a,e,c); | |||
gf_mul(c,a,D); /* 1/s except for sign. FUTURE: simplify using this. */ | |||
gf_sub(b,b,c); | |||
/* a <- s */ | |||
gf_mul(a,e,N); | |||
/* a <- s = e * N * (sqr ? r : r0) | |||
* e^2 r N D = 1 | |||
* 1/s = 1/(e * N * (sqr ? r : r0)) = e * D * (sqr ? 1 : r0) | |||
*/ | |||
gf_mul(a,N,r0); | |||
cond_sel(rN,a,rN,square); | |||
gf_mul(a,rN,e); | |||
gf_mul(c,a,b); | |||
/* Normalize/negate */ | |||
decaf_bool_t neg_s = hibit(a)^~square; | |||
cond_neg(a,neg_s); /* ends up negative if ~square */ | |||
decaf_bool_t sgn_t_over_s = hibit(b)^neg_s; | |||
sgn_t_over_s &= ~gf_eq(N,ZERO); | |||
sgn_t_over_s |= gf_eq(D,ZERO); | |||
/* b <- t */ | |||
cond_sel(b,c,ONE,gf_eq(c,ZERO)); /* 0,0 -> 1,0 */ | |||
/* isogenize */ | |||
gf_sqr(c,a); /* s^2 */ | |||
gf_add(a,a,a); /* 2s */ | |||
@@ -1039,16 +1061,92 @@ void API_NS(point_from_hash_nonuniform) ( | |||
gf_sub(a,ONE,c); | |||
gf_mul(p->y,e,a); /* (1+s^2)(1-s^2) */ | |||
gf_mul(p->z,a,b); /* (1-s^2)t */ | |||
return (~square & 1) | (sgn_t_over_s & 2) | (sgn_r0 & 4) | (over & 8); | |||
} | |||
decaf_bool_t | |||
API_NS(invert_elligator_nonuniform) ( | |||
unsigned char recovered_hash[DECAF_448_SER_BYTES], | |||
const point_t p, | |||
unsigned char hint | |||
) { | |||
decaf_bool_t sgn_s = -(hint & 1), | |||
sgn_t_over_s = -(hint>>1 & 1), | |||
sgn_r0 = -(hint>>2 & 1); | |||
gf a, b, c, d; | |||
gf_mlw ( a, p->y, 1-EDWARDS_D ); | |||
gf_mul ( c, a, p->t ); | |||
gf_mul ( a, p->x, p->z ); | |||
gf_sub ( d, c, a ); | |||
gf_add ( a, p->z, p->y ); | |||
gf_sub ( b, p->z, p->y ); | |||
gf_mul ( c, b, a ); | |||
gf_mlw ( b, c, -EDWARDS_D ); | |||
gf_isqrt ( a, b ); | |||
gf_mlw ( b, a, -EDWARDS_D ); | |||
gf_mul ( c, b, a ); | |||
gf_mul ( a, c, d ); | |||
gf_add ( d, b, b ); | |||
gf_mul ( c, d, p->z ); | |||
cond_neg ( b, sgn_t_over_s^~hibit(c) ); | |||
cond_neg ( c, sgn_t_over_s^~hibit(c) ); | |||
gf_mul ( d, b, p->y ); | |||
gf_add ( a, a, d ); | |||
cond_neg( a, hibit(a)^sgn_s); | |||
/* ok, s = a; c = -t/s */ | |||
gf_mul(b,c,a); | |||
gf_sub(b,ONE,b); /* t+1 */ | |||
gf_sqr(c,a); /* s^2 */ | |||
{ /* identity adjustments */ | |||
/* in case of identity, currently c=0, t=0, b=1, will encode to 1 */ | |||
/* if hint is 0, -> 0 */ | |||
/* if hint is to neg t/s, then go to infinity, effectively set s to 1 */ | |||
decaf_bool_t is_identity = gf_eq(p->x,ZERO); | |||
cond_sel(c,c,ONE,is_identity & sgn_t_over_s); | |||
cond_sel(b,b,ZERO,is_identity & ~sgn_t_over_s & ~sgn_s); /* identity adjust */ | |||
} | |||
gf_mlw(d,c,2*EDWARDS_D-1); /* $d = (2d-a)s^2 */ | |||
gf_add(a,b,d); /* num? */ | |||
gf_sub(d,b,d); /* den? */ | |||
gf_mul(b,a,d); /* n*d */ | |||
cond_sel(a,d,a,sgn_s); | |||
decaf_bool_t succ = gf_isqrt_chk(c,b,DECAF_TRUE); | |||
gf_mul(b,a,c); | |||
cond_neg(b, sgn_r0^hibit(b)); | |||
succ &= ~(gf_eq(b,ZERO) & sgn_r0); | |||
gf_encode(recovered_hash, b); | |||
/* TODO: deal with overflow flag */ | |||
return succ; | |||
} | |||
void API_NS(point_from_hash_uniform) ( | |||
unsigned char API_NS(point_from_hash_uniform) ( | |||
point_t pt, | |||
const unsigned char hashed_data[2*SER_BYTES] | |||
) { | |||
point_t pt2; | |||
API_NS(point_from_hash_nonuniform)(pt,hashed_data); | |||
API_NS(point_from_hash_nonuniform)(pt2,&hashed_data[SER_BYTES]); | |||
unsigned char ret1 = | |||
API_NS(point_from_hash_nonuniform)(pt,hashed_data); | |||
unsigned char ret2 = | |||
API_NS(point_from_hash_nonuniform)(pt2,&hashed_data[SER_BYTES]); | |||
API_NS(point_add)(pt,pt,pt2); | |||
return ret1 | (ret2<<4); | |||
} | |||
decaf_bool_t | |||
API_NS(invert_elligator_uniform) ( | |||
unsigned char partial_hash[2*SER_BYTES], | |||
const point_t p, | |||
unsigned char hint | |||
) { | |||
point_t pt2; | |||
API_NS(point_from_hash_nonuniform)(pt2,&partial_hash[SER_BYTES]); | |||
API_NS(point_sub)(pt2,p,pt2); | |||
return API_NS(invert_elligator_nonuniform)(partial_hash,pt2,hint); | |||
} | |||
decaf_bool_t API_NS(point_valid) ( | |||
@@ -1070,6 +1168,16 @@ decaf_bool_t API_NS(point_valid) ( | |||
return out; | |||
} | |||
void API_NS(point_debugging_2torque) ( | |||
point_t q, | |||
const point_t p | |||
) { | |||
gf_sub(q->x,ZERO,p->x); | |||
gf_sub(q->y,ZERO,p->y); | |||
gf_cpy(q->z,p->z); | |||
gf_cpy(q->t,p->t); | |||
} | |||
static void gf_batch_invert ( | |||
gf *__restrict__ out, | |||
/* const */ gf *in, | |||
@@ -1570,6 +1678,20 @@ void API_NS(point_destroy) ( | |||
decaf_bzero(point, sizeof(point_t)); | |||
} | |||
decaf_bool_t decaf_memeq ( | |||
const void *data1_, | |||
const void *data2_, | |||
size_t size | |||
) { | |||
const unsigned char *data1 = (const unsigned char *)data1_; | |||
const unsigned char *data2 = (const unsigned char *)data2_; | |||
unsigned char ret = 0; | |||
for (; size; size--, data1++, data2++) { | |||
ret |= *data1 ^ *data2; | |||
} | |||
return (((decaf_dword_t)ret) - 1) >> 8; | |||
} | |||
void API_NS(precomputed_destroy) ( | |||
precomputed_s *pre | |||
) { | |||
@@ -299,6 +299,7 @@ int main(int argc, char **argv) { | |||
Point p,q; | |||
Scalar s,t; | |||
SecureBuffer ep, ep2(Point::SER_BYTES*2); | |||
SpongeRng rng(Block("micro-benchmarks")); | |||
printf("\nMicro-benchmarks:\n"); | |||
SHAKE<128> shake1; | |||
@@ -337,6 +338,9 @@ int main(int argc, char **argv) { | |||
for (Benchmark b("Point create/destroy"); b.iter(); ) { Point r; } | |||
for (Benchmark b("Point hash nonuniform"); b.iter(); ) { Point::from_hash(ep); } | |||
for (Benchmark b("Point hash uniform"); b.iter(); ) { Point::from_hash(ep2); } | |||
for (Benchmark b("Point unhash nonuniform"); b.iter(); ) { ignore_result(p.invert_elligator(ep,0)); } | |||
for (Benchmark b("Point unhash uniform"); b.iter(); ) { ignore_result(p.invert_elligator(ep2,0)); } | |||
for (Benchmark b("Point steg"); b.iter(); ) { p.steg_encode(rng); } | |||
for (Benchmark b("Point double scalarmul"); b.iter(); ) { Point::double_scalarmul(p,s,q,t); } | |||
for (Benchmark b("Point precmp scalarmul"); b.iter(); ) { pBase * s; } | |||
/* TODO: scalarmul for verif, etc */ | |||
@@ -152,6 +152,47 @@ static void test_arithmetic() { | |||
} | |||
} | |||
static void test_elligator() { | |||
decaf::SpongeRng rng(decaf::Block("test_elligator")); | |||
Test test("Elligator"); | |||
for (int i=0; i<16; i++) { | |||
decaf::SecureBuffer b1(Point::HASH_BYTES); | |||
Point p = Point::identity(); | |||
if (i>=8) p.debugging_torque_in_place(); | |||
bool succ = p.invert_elligator(b1,i&7); | |||
Point q; | |||
unsigned char hint = q.set_to_hash(b1); | |||
if (succ != ((i&7) != 4) || (q != p) || (succ && (hint != (i&7)))) { | |||
test.fail(); | |||
printf("Elligator test: t=%d, h=%d->%d, q%sp, %s %02x%02x\n", | |||
i/8, i&7, hint, (q==p)?"==":"!=",succ ? "SUCC" : "FAIL", | |||
b1[0], b1[1]); | |||
} | |||
} | |||
for (int i=0; i<NTESTS && test.passing_now; i++) { | |||
size_t len = (i % (2*Point::HASH_BYTES + 3)); | |||
decaf::SecureBuffer b1(len), b2(len); | |||
rng.read(b1); | |||
if (i==1) b1[0] = 1; /* special case test */ | |||
if (len > Point::HASH_BYTES) | |||
memcpy(&b2[Point::HASH_BYTES], &b1[Point::HASH_BYTES], len-Point::HASH_BYTES); | |||
Point s; | |||
unsigned char hint = s.set_to_hash(b1); | |||
if (i&1) s.debugging_torque_in_place(); | |||
bool succ = s.invert_elligator(b2,hint); | |||
if (!succ || memcmp(b1,b2,len)) { | |||
test.fail(); | |||
printf(" Fail elligator inversion i=%d (claimed %s, hint=%d)\n", | |||
i, succ ? "success" : "failure", hint); | |||
} | |||
Point t(rng); | |||
point_check(test,t,t,t,0,0,t,Point::from_hash(t.steg_encode(rng)),"steg round-trip"); | |||
} | |||
} | |||
static void test_ec() { | |||
decaf::SpongeRng rng(decaf::Block("test_ec")); | |||
@@ -175,6 +216,7 @@ static void test_ec() { | |||
point_check(test,p,q,r,0,0,p,Point((decaf::SecureBuffer)p),"round-trip"); | |||
point_check(test,p,q,r,0,0,p+q,q+p,"commute add"); | |||
point_check(test,p,q,r,0,0,(p-q)+q,p,"correct sub"); | |||
point_check(test,p,q,r,0,0,p+(q+r),(p+q)+r,"assoc add"); | |||
point_check(test,p,q,r,0,0,p.times_two(),p+p,"dbl add"); | |||
@@ -236,6 +278,7 @@ int main(int argc, char **argv) { | |||
(void) argc; (void) argv; | |||
Tests<decaf::Ed448>::test_arithmetic(); | |||
Tests<decaf::Ed448>::test_elligator(); | |||
Tests<decaf::Ed448>::test_ec(); | |||
test_decaf(); | |||