Skip to content

Commit 2140da9

Browse files
sipajonasnickreal-or-random
committed
Add secp256k1_scalar_half for halving scalars (+ tests/benchmarks).
Co-authored-by: Jonas Nick <[email protected]> Co-authored-by: Tim Ruffing <[email protected]>
1 parent 1f1bb78 commit 2140da9

6 files changed

+153
-0
lines changed

src/bench_internal.c

+13
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,18 @@ static void bench_scalar_negate(void* arg, int iters) {
9898
}
9999
}
100100

101+
static void bench_scalar_half(void* arg, int iters) {
102+
int i;
103+
bench_inv *data = (bench_inv*)arg;
104+
secp256k1_scalar s = data->scalar[0];
105+
106+
for (i = 0; i < iters; i++) {
107+
secp256k1_scalar_half(&s, &s);
108+
}
109+
110+
data->scalar[0] = s;
111+
}
112+
101113
static void bench_scalar_mul(void* arg, int iters) {
102114
int i;
103115
bench_inv *data = (bench_inv*)arg;
@@ -370,6 +382,7 @@ int main(int argc, char **argv) {
370382
int d = argc == 1; /* default */
371383
print_output_table_header_row();
372384

385+
if (d || have_flag(argc, argv, "scalar") || have_flag(argc, argv, "half")) run_benchmark("scalar_half", bench_scalar_half, bench_setup, NULL, &data, 10, iters*100);
373386
if (d || have_flag(argc, argv, "scalar") || have_flag(argc, argv, "add")) run_benchmark("scalar_add", bench_scalar_add, bench_setup, NULL, &data, 10, iters*100);
374387
if (d || have_flag(argc, argv, "scalar") || have_flag(argc, argv, "negate")) run_benchmark("scalar_negate", bench_scalar_negate, bench_setup, NULL, &data, 10, iters*100);
375388
if (d || have_flag(argc, argv, "scalar") || have_flag(argc, argv, "mul")) run_benchmark("scalar_mul", bench_scalar_mul, bench_setup, NULL, &data, 10, iters*10);

src/scalar.h

+3
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,9 @@ static void secp256k1_scalar_inverse_var(secp256k1_scalar *r, const secp256k1_sc
6767
/** Compute the complement of a scalar (modulo the group order). */
6868
static void secp256k1_scalar_negate(secp256k1_scalar *r, const secp256k1_scalar *a);
6969

70+
/** Multiply a scalar with the multiplicative inverse of 2. */
71+
static void secp256k1_scalar_half(secp256k1_scalar *r, const secp256k1_scalar *a);
72+
7073
/** Check whether a scalar equals zero. */
7174
static int secp256k1_scalar_is_zero(const secp256k1_scalar *a);
7275

src/scalar_4x64_impl.h

+41
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,47 @@ static void secp256k1_scalar_negate(secp256k1_scalar *r, const secp256k1_scalar
199199
secp256k1_scalar_verify(r);
200200
}
201201

202+
static void secp256k1_scalar_half(secp256k1_scalar *r, const secp256k1_scalar *a) {
203+
/* Writing `/` for field division and `//` for integer division, we compute
204+
*
205+
* a/2 = (a - (a&1))/2 + (a&1)/2
206+
* = (a >> 1) + (a&1 ? 1/2 : 0)
207+
* = (a >> 1) + (a&1 ? n//2+1 : 0),
208+
*
209+
* where n is the group order and in the last equality we have used 1/2 = n//2+1 (mod n).
210+
* For n//2, we have the constants SECP256K1_N_H_0, ...
211+
*
212+
* This sum does not overflow. The most extreme case is a = -2, the largest odd scalar. Here:
213+
* - the left summand is: a >> 1 = (a - a&1)/2 = (n-2-1)//2 = (n-3)//2
214+
* - the right summand is: a&1 ? n//2+1 : 0 = n//2+1 = (n-1)//2 + 2//2 = (n+1)//2
215+
* Together they sum to (n-3)//2 + (n+1)//2 = (2n-2)//2 = n - 1, which is less than n.
216+
*/
217+
uint64_t mask = -(uint64_t)(a->d[0] & 1U);
218+
secp256k1_uint128 t;
219+
secp256k1_scalar_verify(a);
220+
221+
secp256k1_u128_from_u64(&t, (a->d[0] >> 1) | (a->d[1] << 63));
222+
secp256k1_u128_accum_u64(&t, (SECP256K1_N_H_0 + 1U) & mask);
223+
r->d[0] = secp256k1_u128_to_u64(&t); secp256k1_u128_rshift(&t, 64);
224+
secp256k1_u128_accum_u64(&t, (a->d[1] >> 1) | (a->d[2] << 63));
225+
secp256k1_u128_accum_u64(&t, SECP256K1_N_H_1 & mask);
226+
r->d[1] = secp256k1_u128_to_u64(&t); secp256k1_u128_rshift(&t, 64);
227+
secp256k1_u128_accum_u64(&t, (a->d[2] >> 1) | (a->d[3] << 63));
228+
secp256k1_u128_accum_u64(&t, SECP256K1_N_H_2 & mask);
229+
r->d[2] = secp256k1_u128_to_u64(&t); secp256k1_u128_rshift(&t, 64);
230+
r->d[3] = secp256k1_u128_to_u64(&t) + (a->d[3] >> 1) + (SECP256K1_N_H_3 & mask);
231+
#ifdef VERIFY
232+
/* The line above only computed the bottom 64 bits of r->d[3]; redo the computation
233+
* in full 128 bits to make sure the top 64 bits are indeed zero. */
234+
secp256k1_u128_accum_u64(&t, a->d[3] >> 1);
235+
secp256k1_u128_accum_u64(&t, SECP256K1_N_H_3 & mask);
236+
secp256k1_u128_rshift(&t, 64);
237+
VERIFY_CHECK(secp256k1_u128_to_u64(&t) == 0);
238+
239+
secp256k1_scalar_verify(r);
240+
#endif
241+
}
242+
202243
SECP256K1_INLINE static int secp256k1_scalar_is_one(const secp256k1_scalar *a) {
203244
secp256k1_scalar_verify(a);
204245

src/scalar_8x32_impl.h

+49
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,55 @@ static void secp256k1_scalar_negate(secp256k1_scalar *r, const secp256k1_scalar
245245
secp256k1_scalar_verify(r);
246246
}
247247

248+
static void secp256k1_scalar_half(secp256k1_scalar *r, const secp256k1_scalar *a) {
249+
/* Writing `/` for field division and `//` for integer division, we compute
250+
*
251+
* a/2 = (a - (a&1))/2 + (a&1)/2
252+
* = (a >> 1) + (a&1 ? 1/2 : 0)
253+
* = (a >> 1) + (a&1 ? n//2+1 : 0),
254+
*
255+
* where n is the group order and in the last equality we have used 1/2 = n//2+1 (mod n).
256+
* For n//2, we have the constants SECP256K1_N_H_0, ...
257+
*
258+
* This sum does not overflow. The most extreme case is a = -2, the largest odd scalar. Here:
259+
* - the left summand is: a >> 1 = (a - a&1)/2 = (n-2-1)//2 = (n-3)//2
260+
* - the right summand is: a&1 ? n//2+1 : 0 = n//2+1 = (n-1)//2 + 2//2 = (n+1)//2
261+
* Together they sum to (n-3)//2 + (n+1)//2 = (2n-2)//2 = n - 1, which is less than n.
262+
*/
263+
uint32_t mask = -(uint32_t)(a->d[0] & 1U);
264+
uint64_t t = (uint32_t)((a->d[0] >> 1) | (a->d[1] << 31));
265+
secp256k1_scalar_verify(a);
266+
267+
t += (SECP256K1_N_H_0 + 1U) & mask;
268+
r->d[0] = t; t >>= 32;
269+
t += (uint32_t)((a->d[1] >> 1) | (a->d[2] << 31));
270+
t += SECP256K1_N_H_1 & mask;
271+
r->d[1] = t; t >>= 32;
272+
t += (uint32_t)((a->d[2] >> 1) | (a->d[3] << 31));
273+
t += SECP256K1_N_H_2 & mask;
274+
r->d[2] = t; t >>= 32;
275+
t += (uint32_t)((a->d[3] >> 1) | (a->d[4] << 31));
276+
t += SECP256K1_N_H_3 & mask;
277+
r->d[3] = t; t >>= 32;
278+
t += (uint32_t)((a->d[4] >> 1) | (a->d[5] << 31));
279+
t += SECP256K1_N_H_4 & mask;
280+
r->d[4] = t; t >>= 32;
281+
t += (uint32_t)((a->d[5] >> 1) | (a->d[6] << 31));
282+
t += SECP256K1_N_H_5 & mask;
283+
r->d[5] = t; t >>= 32;
284+
t += (uint32_t)((a->d[6] >> 1) | (a->d[7] << 31));
285+
t += SECP256K1_N_H_6 & mask;
286+
r->d[6] = t; t >>= 32;
287+
r->d[7] = (uint32_t)t + (uint32_t)(a->d[7] >> 1) + (SECP256K1_N_H_7 & mask);
288+
#ifdef VERIFY
289+
/* The line above only computed the bottom 32 bits of r->d[7]. Redo the computation
290+
* in full 64 bits to make sure the top 32 bits are indeed zero. */
291+
VERIFY_CHECK((t + (a->d[7] >> 1) + (SECP256K1_N_H_7 & mask)) >> 32 == 0);
292+
293+
secp256k1_scalar_verify(r);
294+
#endif
295+
}
296+
248297
SECP256K1_INLINE static int secp256k1_scalar_is_one(const secp256k1_scalar *a) {
249298
secp256k1_scalar_verify(a);
250299

src/scalar_low_impl.h

+8
Original file line numberDiff line numberDiff line change
@@ -205,4 +205,12 @@ static void secp256k1_scalar_inverse_var(secp256k1_scalar *r, const secp256k1_sc
205205
secp256k1_scalar_verify(r);
206206
}
207207

208+
static void secp256k1_scalar_half(secp256k1_scalar *r, const secp256k1_scalar *a) {
209+
secp256k1_scalar_verify(a);
210+
211+
*r = (*a + ((-(uint32_t)(*a & 1)) & EXHAUSTIVE_TEST_ORDER)) >> 1;
212+
213+
secp256k1_scalar_verify(r);
214+
}
215+
208216
#endif /* SECP256K1_SCALAR_REPR_IMPL_H */

src/tests.c

+39
Original file line numberDiff line numberDiff line change
@@ -2285,6 +2285,13 @@ static void scalar_test(void) {
22852285
CHECK(secp256k1_scalar_eq(&r1, &secp256k1_scalar_zero));
22862286
}
22872287

2288+
{
2289+
/* Test halving. */
2290+
secp256k1_scalar r;
2291+
secp256k1_scalar_add(&r, &s, &s);
2292+
secp256k1_scalar_half(&r, &r);
2293+
CHECK(secp256k1_scalar_eq(&r, &s));
2294+
}
22882295
}
22892296

22902297
static void run_scalar_set_b32_seckey_tests(void) {
@@ -2337,6 +2344,38 @@ static void run_scalar_tests(void) {
23372344
CHECK(secp256k1_scalar_is_zero(&o));
23382345
}
23392346

2347+
{
2348+
/* Test that halving and doubling roundtrips on some fixed values. */
2349+
static const secp256k1_scalar HALF_TESTS[] = {
2350+
/* 0 */
2351+
SECP256K1_SCALAR_CONST(0, 0, 0, 0, 0, 0, 0, 0),
2352+
/* 1 */
2353+
SECP256K1_SCALAR_CONST(0, 0, 0, 0, 0, 0, 0, 1),
2354+
/* -1 */
2355+
SECP256K1_SCALAR_CONST(0xfffffffful, 0xfffffffful, 0xfffffffful, 0xfffffffeul, 0xbaaedce6ul, 0xaf48a03bul, 0xbfd25e8cul, 0xd0364140ul),
2356+
/* -2 (largest odd value) */
2357+
SECP256K1_SCALAR_CONST(0xfffffffful, 0xfffffffful, 0xfffffffful, 0xfffffffeul, 0xbaaedce6ul, 0xaf48a03bul, 0xbfd25e8cul, 0xd036413Ful),
2358+
/* Half the secp256k1 order */
2359+
SECP256K1_SCALAR_CONST(0x7ffffffful, 0xfffffffful, 0xfffffffful, 0xfffffffful, 0x5d576e73ul, 0x57a4501dul, 0xdfe92f46ul, 0x681b20a0ul),
2360+
/* Half the secp256k1 order + 1 */
2361+
SECP256K1_SCALAR_CONST(0x7ffffffful, 0xfffffffful, 0xfffffffful, 0xfffffffful, 0x5d576e73ul, 0x57a4501dul, 0xdfe92f46ul, 0x681b20a1ul),
2362+
/* 2^255 */
2363+
SECP256K1_SCALAR_CONST(0x80000000ul, 0, 0, 0, 0, 0, 0, 0),
2364+
/* 2^255 - 1 */
2365+
SECP256K1_SCALAR_CONST(0x7ffffffful, 0xfffffffful, 0xfffffffful, 0xfffffffful, 0xfffffffful, 0xfffffffful, 0xfffffffful, 0xfffffffful),
2366+
};
2367+
unsigned n;
2368+
for (n = 0; n < sizeof(HALF_TESTS) / sizeof(HALF_TESTS[0]); ++n) {
2369+
secp256k1_scalar s;
2370+
secp256k1_scalar_half(&s, &HALF_TESTS[n]);
2371+
secp256k1_scalar_add(&s, &s, &s);
2372+
CHECK(secp256k1_scalar_eq(&s, &HALF_TESTS[n]));
2373+
secp256k1_scalar_add(&s, &s, &s);
2374+
secp256k1_scalar_half(&s, &s);
2375+
CHECK(secp256k1_scalar_eq(&s, &HALF_TESTS[n]));
2376+
}
2377+
}
2378+
23402379
{
23412380
/* Does check_overflow check catch all ones? */
23422381
static const secp256k1_scalar overflowed = SECP256K1_SCALAR_CONST(

0 commit comments

Comments
 (0)