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_desc" : "2^255 - 19", | ||||
"gf_shortname" : "25519", | "gf_shortname" : "25519", | ||||
"gf_impl_bits" : 320, | "gf_impl_bits" : 320, | ||||
"gf_lit_limb_bits" : 51 | |||||
"gf_lit_limb_bits" : 51, | |||||
"elligator_onto" : 0 | |||||
}, | }, | ||||
"p448" : { | "p448" : { | ||||
"gf_desc" : "2^448 - 2^224 - 1", | "gf_desc" : "2^448 - 2^224 - 1", | ||||
"gf_shortname" : "448", | "gf_shortname" : "448", | ||||
"gf_impl_bits" : 512, | "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. */ | /** Number of bytes in a serialized point. */ | ||||
#define $(C_NS)_SER_BYTES $((gf_bits-2)/8 + 1) | #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. */ | /** Number of bytes in a serialized scalar. */ | ||||
#define $(C_NS)_SCALAR_BYTES $((scalar_bits-1)/8 + 1) | #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 */ | /** Number of bytes in an x$(gf_shortname) public key */ | ||||
#define X$(gf_shortname)_PUBLIC_BYTES $((gf_bits-1)/8 + 1) | #define X$(gf_shortname)_PUBLIC_BYTES $((gf_bits-1)/8 + 1) | ||||
@@ -594,7 +602,7 @@ void $(c_ns)_point_debugging_pscale ( | |||||
void | void | ||||
$(c_ns)_point_from_hash_nonuniform ( | $(c_ns)_point_from_hash_nonuniform ( | ||||
$(c_ns)_point_t pt, | $(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; | ) API_VIS NONNULL NOINLINE; | ||||
/** | /** | ||||
@@ -607,7 +615,7 @@ $(c_ns)_point_from_hash_nonuniform ( | |||||
*/ | */ | ||||
void $(c_ns)_point_from_hash_uniform ( | void $(c_ns)_point_from_hash_uniform ( | ||||
$(c_ns)_point_t pt, | $(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; | ) API_VIS NONNULL NOINLINE; | ||||
/** | /** | ||||
@@ -630,9 +638,9 @@ void $(c_ns)_point_from_hash_uniform ( | |||||
*/ | */ | ||||
decaf_error_t | decaf_error_t | ||||
$(c_ns)_invert_elligator_nonuniform ( | $(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, | const $(c_ns)_point_t pt, | ||||
uint16_t which | |||||
uint32_t which | |||||
) API_VIS NONNULL NOINLINE WARN_UNUSED; | ) API_VIS NONNULL NOINLINE WARN_UNUSED; | ||||
/** | /** | ||||
@@ -655,9 +663,9 @@ $(c_ns)_invert_elligator_nonuniform ( | |||||
*/ | */ | ||||
decaf_error_t | decaf_error_t | ||||
$(c_ns)_invert_elligator_uniform ( | $(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, | const $(c_ns)_point_t pt, | ||||
uint16_t which | |||||
uint32_t which | |||||
) API_VIS NONNULL NOINLINE WARN_UNUSED; | ) API_VIS NONNULL NOINLINE WARN_UNUSED; | ||||
/** | /** | ||||
@@ -228,10 +228,24 @@ public: | |||||
static const size_t SER_BYTES = $(C_NS)_SER_BYTES; | static const size_t SER_BYTES = $(C_NS)_SER_BYTES; | ||||
/** Bytes required for hash */ | /** 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; | 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. */ | /** The c-level object. */ | ||||
Wrapped p; | Wrapped p; | ||||
@@ -441,7 +455,7 @@ public: | |||||
* or leave buf unmodified and return DECAF_FAILURE. | * or leave buf unmodified and return DECAF_FAILURE. | ||||
*/ | */ | ||||
inline decaf_error_t invert_elligator ( | inline decaf_error_t invert_elligator ( | ||||
Buffer buf, uint16_t hint | |||||
Buffer buf, uint32_t hint | |||||
) const NOEXCEPT { | ) const NOEXCEPT { | ||||
unsigned char buf2[2*HASH_BYTES]; | unsigned char buf2[2*HASH_BYTES]; | ||||
memset(buf2,0,sizeof(buf2)); | memset(buf2,0,sizeof(buf2)); | ||||
@@ -468,8 +482,10 @@ public: | |||||
SecureBuffer out(STEG_BYTES); | SecureBuffer out(STEG_BYTES); | ||||
decaf_error_t done; | decaf_error_t done; | ||||
do { | 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)); | } while (!decaf_successful(done)); | ||||
return out; | return out; | ||||
} | } | ||||
@@ -25,7 +25,7 @@ void API_NS(precompute_wnafs) ( | |||||
struct niels_s *out, | struct niels_s *out, | ||||
const API_NS(point_t) base | 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]; | unsigned char ser[X_SER_BYTES]; | ||||
gf_serialize(ser,f,1); | gf_serialize(ser,f,1); | ||||
int b=0, i, comma=0; | int b=0, i, comma=0; | ||||
@@ -89,20 +89,49 @@ void API_NS(point_from_hash_uniform) ( | |||||
API_NS(point_add)(pt,pt,pt2); | 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 | decaf_error_t | ||||
API_NS(invert_elligator_nonuniform) ( | API_NS(invert_elligator_nonuniform) ( | ||||
unsigned char recovered_hash[SER_BYTES], | unsigned char recovered_hash[SER_BYTES], | ||||
const point_t p, | const point_t p, | ||||
uint16_t hint_ | |||||
uint32_t hint_ | |||||
) { | ) { | ||||
mask_t hint = hint_; | mask_t hint = hint_; | ||||
mask_t sgn_s = -(hint & 1), | mask_t sgn_s = -(hint & 1), | ||||
sgn_t_over_s = -(hint>>1 & 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); | sgn_ed_T = -(hint>>3 & 1); | ||||
gf a, b, c, d; | gf a, b, c, d; | ||||
API_NS(deisogenize)(a,c,p,sgn_s,sgn_t_over_s,sgn_ed_T); | 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 */ | /* ok, a = s; c = -t/s */ | ||||
gf_mul(b,c,a); | gf_mul(b,c,a); | ||||
gf_sub(b,ONE,b); /* t+1 */ | 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)); | gf_cond_neg(b, sgn_r0^gf_hibit(b)); | ||||
succ &= ~(gf_eq(b,ZERO) & sgn_r0); | 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)); | return decaf_succeed_if(mask_to_bool(succ)); | ||||
} | } | ||||
@@ -140,7 +176,7 @@ decaf_error_t | |||||
API_NS(invert_elligator_uniform) ( | API_NS(invert_elligator_uniform) ( | ||||
unsigned char partial_hash[2*SER_BYTES], | unsigned char partial_hash[2*SER_BYTES], | ||||
const point_t p, | const point_t p, | ||||
uint16_t hint | |||||
uint32_t hint | |||||
) { | ) { | ||||
point_t pt2; | point_t pt2; | ||||
API_NS(point_from_hash_nonuniform)(pt2,&partial_hash[SER_BYTES]); | 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); | SpongeRng rng(Block("test_elligator"),SpongeRng::DETERMINISTIC); | ||||
Test test("Elligator"); | Test test("Elligator"); | ||||
const int NHINTS = Group::REMOVED_COFACTOR * 2; | |||||
const int NHINTS = 1<<Point::INVERT_ELLIGATOR_WHICH_BITS; | |||||
SecureBuffer *alts[NHINTS]; | SecureBuffer *alts[NHINTS]; | ||||
bool successes[NHINTS]; | bool successes[NHINTS]; | ||||
SecureBuffer *alts2[NHINTS]; | SecureBuffer *alts2[NHINTS]; | ||||
bool successes2[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)); | size_t len = (i % (2*Point::HASH_BYTES + 3)); | ||||
SecureBuffer b1(len); | SecureBuffer b1(len); | ||||
if (i!=Point::HASH_BYTES) rng.read(b1); /* special test case */ | 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 */ | /* Pathological cases */ | ||||
if (i==1) b1[0] = 1; | if (i==1) b1[0] = 1; | ||||
@@ -293,10 +292,6 @@ static void test_elligator() { | |||||
Point t(rng); | Point t(rng); | ||||
point_check(test,t,t,t,0,0,t,Point::from_hash(t.steg_encode(rng)),"steg round-trip"); | point_check(test,t,t,t,0,0,t,Point::from_hash(t.steg_encode(rng)),"steg round-trip"); | ||||
} | } | ||||
} | } | ||||