Before, invert_elligator would invert to a gf, which wouldnt be a uniformly random string because, eg, curve25519 gfs only have 255 bits out of 256. Now add a random multiple of p. This still wont work for future curves that have a field size of 1 mod 8, because those curves use elligator with no high bit set, but its a startmaster
@@ -8,13 +8,15 @@ field_data = { | |||
"gf_desc" : "2^255 - 19", | |||
"gf_shortname" : "25519", | |||
"gf_impl_bits" : 320, | |||
"gf_lit_limb_bits" : 51 | |||
"gf_lit_limb_bits" : 51, | |||
"elligator_onto" : 0 | |||
}, | |||
"p448" : { | |||
"gf_desc" : "2^448 - 2^224 - 1", | |||
"gf_shortname" : "448", | |||
"gf_impl_bits" : 512, | |||
"gf_lit_limb_bits" : 56 | |||
"gf_lit_limb_bits" : 56, | |||
"elligator_onto" : 0 | |||
} | |||
} | |||
@@ -26,9 +26,17 @@ typedef struct gf_$(gf_shortname)_s { | |||
/** Number of bytes in a serialized point. */ | |||
#define $(C_NS)_SER_BYTES $((gf_bits-2)/8 + 1) | |||
/** Number of bytes in an elligated point. For now set the same as SER_BYTES | |||
* but could be different for other curves. | |||
*/ | |||
#define $(C_NS)_HASH_BYTES $((gf_bits-2)/8 + 1) | |||
/** Number of bytes in a serialized scalar. */ | |||
#define $(C_NS)_SCALAR_BYTES $((scalar_bits-1)/8 + 1) | |||
/** Number of bits in the "which" field of an elligator inverse */ | |||
#define $(C_NS)_INVERT_ELLIGATOR_WHICH_BITS $(ceil_log2(cofactor) + 7 + elligator_onto - ((gf_bits-2) % 8)) | |||
/** Number of bytes in an x$(gf_shortname) public key */ | |||
#define X$(gf_shortname)_PUBLIC_BYTES $((gf_bits-1)/8 + 1) | |||
@@ -594,7 +602,7 @@ void $(c_ns)_point_debugging_pscale ( | |||
void | |||
$(c_ns)_point_from_hash_nonuniform ( | |||
$(c_ns)_point_t pt, | |||
const unsigned char hashed_data[$(C_NS)_SER_BYTES] | |||
const unsigned char hashed_data[$(C_NS)_HASH_BYTES] | |||
) API_VIS NONNULL NOINLINE; | |||
/** | |||
@@ -607,7 +615,7 @@ $(c_ns)_point_from_hash_nonuniform ( | |||
*/ | |||
void $(c_ns)_point_from_hash_uniform ( | |||
$(c_ns)_point_t pt, | |||
const unsigned char hashed_data[2*$(C_NS)_SER_BYTES] | |||
const unsigned char hashed_data[2*$(C_NS)_HASH_BYTES] | |||
) API_VIS NONNULL NOINLINE; | |||
/** | |||
@@ -630,9 +638,9 @@ void $(c_ns)_point_from_hash_uniform ( | |||
*/ | |||
decaf_error_t | |||
$(c_ns)_invert_elligator_nonuniform ( | |||
unsigned char recovered_hash[$(C_NS)_SER_BYTES], | |||
unsigned char recovered_hash[$(C_NS)_HASH_BYTES], | |||
const $(c_ns)_point_t pt, | |||
uint16_t which | |||
uint32_t which | |||
) API_VIS NONNULL NOINLINE WARN_UNUSED; | |||
/** | |||
@@ -655,9 +663,9 @@ $(c_ns)_invert_elligator_nonuniform ( | |||
*/ | |||
decaf_error_t | |||
$(c_ns)_invert_elligator_uniform ( | |||
unsigned char recovered_hash[2*$(C_NS)_SER_BYTES], | |||
unsigned char recovered_hash[2*$(C_NS)_HASH_BYTES], | |||
const $(c_ns)_point_t pt, | |||
uint16_t which | |||
uint32_t which | |||
) API_VIS NONNULL NOINLINE WARN_UNUSED; | |||
/** | |||
@@ -228,10 +228,24 @@ public: | |||
static const size_t SER_BYTES = $(C_NS)_SER_BYTES; | |||
/** Bytes required for hash */ | |||
static const size_t HASH_BYTES = SER_BYTES; | |||
static const size_t HASH_BYTES = $(C_NS)_HASH_BYTES; | |||
/** Size of a stegged element */ | |||
/** | |||
* Size of a stegged element. | |||
* | |||
* FUTURE: You can use HASH_BYTES * 3/2 (or more likely much less, eg HASH_BYTES + 8) | |||
* with a random oracle hash function, by hash-expanding everything past the first | |||
* HASH_BYTES of the element. However, since the internal C invert_elligator is not | |||
* tied to a hash function, I didn't want to tie the C++ wrapper to a hash function | |||
* either. But it might be a good idea to do this in the future, either with STROBE | |||
* or something else. | |||
* | |||
* Then again, calling invert_elligator at all is super niche, so maybe who cares? | |||
*/ | |||
static const size_t STEG_BYTES = HASH_BYTES * 2; | |||
/** Number of bits in invert_elligator which are actually used. */ | |||
static const unsigned int INVERT_ELLIGATOR_WHICH_BITS = $(C_NS)_INVERT_ELLIGATOR_WHICH_BITS; | |||
/** The c-level object. */ | |||
Wrapped p; | |||
@@ -441,7 +455,7 @@ public: | |||
* or leave buf unmodified and return DECAF_FAILURE. | |||
*/ | |||
inline decaf_error_t invert_elligator ( | |||
Buffer buf, uint16_t hint | |||
Buffer buf, uint32_t hint | |||
) const NOEXCEPT { | |||
unsigned char buf2[2*HASH_BYTES]; | |||
memset(buf2,0,sizeof(buf2)); | |||
@@ -468,8 +482,10 @@ public: | |||
SecureBuffer out(STEG_BYTES); | |||
decaf_error_t done; | |||
do { | |||
rng.read(Buffer(out).slice(HASH_BYTES-1,STEG_BYTES-HASH_BYTES+1)); | |||
done = invert_elligator(out, out[HASH_BYTES-1]); | |||
rng.read(Buffer(out).slice(HASH_BYTES-4,STEG_BYTES-HASH_BYTES+1)); | |||
uint32_t hint = 0; | |||
for (int i=0; i<4; i++) { hint |= uint32_t(out[HASH_BYTES-4+i])<<(8*i); } | |||
done = invert_elligator(out, hint); | |||
} while (!decaf_successful(done)); | |||
return out; | |||
} | |||
@@ -25,7 +25,7 @@ void API_NS(precompute_wnafs) ( | |||
struct niels_s *out, | |||
const API_NS(point_t) base | |||
); | |||
static void field_print(const gf f) { /* UNIFY */ | |||
static void field_print(const gf f) { | |||
unsigned char ser[X_SER_BYTES]; | |||
gf_serialize(ser,f,1); | |||
int b=0, i, comma=0; | |||
@@ -89,20 +89,49 @@ void API_NS(point_from_hash_uniform) ( | |||
API_NS(point_add)(pt,pt,pt2); | |||
} | |||
/* Elligator_onto: | |||
* Make elligator-inverse onto at the cost of roughly halving the success probability. | |||
* Currently no effect for curves with field size 1 bit mod 8 (where the top bit | |||
* is chopped off). FUTURE MAGIC: automatic at least for brainpool-style curves; support | |||
* log p == 1 mod 8 brainpool curves maybe? | |||
*/ | |||
#define MAX(A,B) (((A)>(B)) ? (A) : (B)) | |||
#define PKP_MASK ((1<<(MAX(8*SER_BYTES + $(elligator_onto) - $(gf_bits),0)))-1) | |||
#if PKP_MASK != 0 | |||
static UNUSED mask_t plus_k_p ( | |||
uint8_t x[SER_BYTES], | |||
uint32_t factor_ | |||
) { | |||
uint32_t carry = 0; | |||
uint64_t factor = factor_; | |||
const uint8_t p[SER_BYTES] = { $(ser(modulus,8)) }; | |||
for (unsigned int i=0; i<SER_BYTES; i++) { | |||
uint64_t tmp = carry + p[i] * factor + x[i]; | |||
/* tmp <= 2^32-1 + (2^32-1)*(2^8-1) + (2^8-1) = 2^40-1 */ | |||
x[i] = tmp; carry = tmp>>8; | |||
} | |||
return word_is_zero(carry); | |||
} | |||
#endif | |||
decaf_error_t | |||
API_NS(invert_elligator_nonuniform) ( | |||
unsigned char recovered_hash[SER_BYTES], | |||
const point_t p, | |||
uint16_t hint_ | |||
uint32_t hint_ | |||
) { | |||
mask_t hint = hint_; | |||
mask_t sgn_s = -(hint & 1), | |||
sgn_t_over_s = -(hint>>1 & 1), | |||
sgn_r0 = -(hint>>2 & 1), /* FIXME: but it's SER_BYTES ... */ | |||
sgn_r0 = -(hint>>2 & 1), | |||
sgn_ed_T = -(hint>>3 & 1); | |||
gf a, b, c, d; | |||
API_NS(deisogenize)(a,c,p,sgn_s,sgn_t_over_s,sgn_ed_T); | |||
#if $(gf_bits) == 8*SER_BYTES + 1 /* p521. */ | |||
sgn_r0 = 0; | |||
#endif | |||
/* ok, a = s; c = -t/s */ | |||
gf_mul(b,c,a); | |||
gf_sub(b,ONE,b); /* t+1 */ | |||
@@ -127,12 +156,19 @@ API_NS(invert_elligator_nonuniform) ( | |||
gf_cond_neg(b, sgn_r0^gf_hibit(b)); | |||
succ &= ~(gf_eq(b,ZERO) & sgn_r0); | |||
#if COFACTOR == 8 | |||
succ &= ~(is_identity & sgn_ed_T); /* NB: there are no preimages of rotated identity. */ | |||
#endif | |||
#if COFACTOR == 8 | |||
succ &= ~(is_identity & sgn_ed_T); /* NB: there are no preimages of rotated identity. */ | |||
#endif | |||
gf_serialize(recovered_hash,b,1); /* FIXME: ,0 */ | |||
/* TODO: deal with overflow flag */ | |||
#if $(gf_bits) == 8*SER_BYTES + 1 /* p521 */ | |||
gf_serialize(recovered_hash,b,0); | |||
#else | |||
gf_serialize(recovered_hash,b,1); | |||
#if PKP_MASK != 0 | |||
/* Add a multiple of p to make the result either almost-onto or completely onto. */ | |||
succ &= plus_k_p(recovered_hash, (hint >> ((COFACTOR==8)?4:3)) & PKP_MASK); | |||
#endif | |||
#endif | |||
return decaf_succeed_if(mask_to_bool(succ)); | |||
} | |||
@@ -140,7 +176,7 @@ decaf_error_t | |||
API_NS(invert_elligator_uniform) ( | |||
unsigned char partial_hash[2*SER_BYTES], | |||
const point_t p, | |||
uint16_t hint | |||
uint32_t hint | |||
) { | |||
point_t pt2; | |||
API_NS(point_from_hash_nonuniform)(pt2,&partial_hash[SER_BYTES]); | |||
@@ -201,17 +201,16 @@ static void test_elligator() { | |||
SpongeRng rng(Block("test_elligator"),SpongeRng::DETERMINISTIC); | |||
Test test("Elligator"); | |||
const int NHINTS = Group::REMOVED_COFACTOR * 2; | |||
const int NHINTS = 1<<Point::INVERT_ELLIGATOR_WHICH_BITS; | |||
SecureBuffer *alts[NHINTS]; | |||
bool successes[NHINTS]; | |||
SecureBuffer *alts2[NHINTS]; | |||
bool successes2[NHINTS]; | |||
for (int i=0; i<NTESTS/10 && test.passing_now; i++) { | |||
for (int i=0; i<NTESTS/10 && (i<10 || test.passing_now); i++) { | |||
size_t len = (i % (2*Point::HASH_BYTES + 3)); | |||
SecureBuffer b1(len); | |||
if (i!=Point::HASH_BYTES) rng.read(b1); /* special test case */ | |||
if (len >= Point::HASH_BYTES) b1[Point::HASH_BYTES-1] &= 0x7F; // FIXME MAGIC | |||
/* Pathological cases */ | |||
if (i==1) b1[0] = 1; | |||
@@ -293,10 +292,6 @@ static void test_elligator() { | |||
Point t(rng); | |||
point_check(test,t,t,t,0,0,t,Point::from_hash(t.steg_encode(rng)),"steg round-trip"); | |||
} | |||
} | |||