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"); | |||
| } | |||
| } | |||