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