Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Elligator Square module #982

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 12 additions & 3 deletions .cirrus.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ env:
ECDH: no
RECOVERY: no
SCHNORRSIG: no
ELLSQ: no
### test options
SECP256K1_TEST_ITERS:
BENCH: yes
Expand Down Expand Up @@ -67,12 +68,12 @@ task:
<< : *LINUX_CONTAINER
matrix: &ENV_MATRIX
- env: {WIDEMUL: int64, RECOVERY: yes}
- env: {WIDEMUL: int64, ECDH: yes, SCHNORRSIG: yes}
- env: {WIDEMUL: int64, ECDH: yes, SCHNORRSIG: yes, ELLSQ: yes}
- env: {WIDEMUL: int128}
- env: {WIDEMUL: int128, RECOVERY: yes, SCHNORRSIG: yes}
- env: {WIDEMUL: int128, RECOVERY: yes, SCHNORRSIG: yes, ELLSQ: yes}
- env: {WIDEMUL: int128, ECDH: yes, SCHNORRSIG: yes}
- env: {WIDEMUL: int128, ASM: x86_64}
- env: { RECOVERY: yes, SCHNORRSIG: yes}
- env: { RECOVERY: yes, SCHNORRSIG: yes, ELLSQ: yes}
- env: {BUILD: distcheck, WITH_VALGRIND: no, CTIMETEST: no, BENCH: no}
- env: {CPPFLAGS: -DDETERMINISTIC}
- env: {CFLAGS: -O0, CTIMETEST: no}
Expand All @@ -94,6 +95,7 @@ task:
env:
HOST: i686-linux-gnu
ECDH: yes
ELLSQ: yes
RECOVERY: yes
SCHNORRSIG: yes
matrix:
Expand Down Expand Up @@ -176,6 +178,7 @@ task:
HOST: s390x-linux-gnu
WITH_VALGRIND: no
ECDH: yes
ELLSQ: yes
RECOVERY: yes
SCHNORRSIG: yes
CTIMETEST: no
Expand All @@ -195,6 +198,7 @@ task:
HOST: arm-linux-gnueabihf
WITH_VALGRIND: no
ECDH: yes
ELLSQ: tes
RECOVERY: yes
SCHNORRSIG: yes
CTIMETEST: no
Expand All @@ -215,6 +219,7 @@ task:
HOST: aarch64-linux-gnu
WITH_VALGRIND: no
ECDH: yes
ELLSQ: yes
RECOVERY: yes
SCHNORRSIG: yes
CTIMETEST: no
Expand All @@ -232,6 +237,7 @@ task:
HOST: powerpc64le-linux-gnu
WITH_VALGRIND: no
ECDH: yes
ELLSQ: yes
RECOVERY: yes
SCHNORRSIG: yes
CTIMETEST: no
Expand All @@ -249,6 +255,7 @@ task:
HOST: x86_64-w64-mingw32
WITH_VALGRIND: no
ECDH: yes
ELLSQ: yes
RECOVERY: yes
SCHNORRSIG: yes
CTIMETEST: no
Expand All @@ -262,6 +269,7 @@ task:
<< : *LINUX_CONTAINER
env:
ECDH: yes
ELLSQ: yes
RECOVERY: yes
SCHNORRSIG: yes
CTIMETEST: no
Expand Down Expand Up @@ -311,6 +319,7 @@ task:
MAKEFLAGS: -j4 CC=g++ CFLAGS=-fpermissive\ -g
WERROR_CFLAGS:
ECDH: yes
ELLSQ: yes
RECOVERY: yes
SCHNORRSIG: yes
<< : *MERGE_BASE
Expand Down
4 changes: 4 additions & 0 deletions Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -228,3 +228,7 @@ endif
if ENABLE_MODULE_SCHNORRSIG
include src/modules/schnorrsig/Makefile.am.include
endif

if ENABLE_MODULE_ELLSQ
include src/modules/ellsq/Makefile.am.include
endif
1 change: 1 addition & 0 deletions ci/cirrus.sh
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ valgrind --version || true
--with-ecmult-window="$ECMULTWINDOW" \
--with-ecmult-gen-precision="$ECMULTGENPRECISION" \
--enable-module-ecdh="$ECDH" --enable-module-recovery="$RECOVERY" \
--enable-module-ellsq="$ELLSQ" \
--enable-module-schnorrsig="$SCHNORRSIG" \
--enable-examples="$EXAMPLES" \
--with-valgrind="$WITH_VALGRIND" \
Expand Down
11 changes: 11 additions & 0 deletions configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,11 @@ AC_ARG_ENABLE(module_schnorrsig,
AS_HELP_STRING([--enable-module-schnorrsig],[enable schnorrsig module [default=no]]), [],
[SECP_SET_DEFAULT([enable_module_schnorrsig], [no], [yes])])

AC_ARG_ENABLE(module_ellsq,
AS_HELP_STRING([--enable-module-ellsq],[enable Elligator^2 module (experimental)]),
[enable_module_ellsq=$enableval],
[enable_module_ellsq=no])

AC_ARG_ENABLE(external_default_callbacks,
AS_HELP_STRING([--enable-external-default-callbacks],[enable external default callback functions [default=no]]), [],
[SECP_SET_DEFAULT([enable_external_default_callbacks], [no], [no])])
Expand Down Expand Up @@ -352,6 +357,10 @@ if test x"$enable_module_extrakeys" = x"yes"; then
AC_DEFINE(ENABLE_MODULE_EXTRAKEYS, 1, [Define this symbol to enable the extrakeys module])
fi

if test x"$enable_module_ellsq" = x"yes"; then
AC_DEFINE(ENABLE_MODULE_ELLSQ, 1, [Define this symbol to enable the Elligator^2 module])
fi

if test x"$enable_external_default_callbacks" = x"yes"; then
AC_DEFINE(USE_EXTERNAL_DEFAULT_CALLBACKS, 1, [Define this symbol if an external implementation of the default callbacks is used])
fi
Expand Down Expand Up @@ -391,6 +400,7 @@ AM_CONDITIONAL([ENABLE_MODULE_ECDH], [test x"$enable_module_ecdh" = x"yes"])
AM_CONDITIONAL([ENABLE_MODULE_RECOVERY], [test x"$enable_module_recovery" = x"yes"])
AM_CONDITIONAL([ENABLE_MODULE_EXTRAKEYS], [test x"$enable_module_extrakeys" = x"yes"])
AM_CONDITIONAL([ENABLE_MODULE_SCHNORRSIG], [test x"$enable_module_schnorrsig" = x"yes"])
AM_CONDITIONAL([ENABLE_MODULE_ELLSQ], [test x"$enable_module_ellsq" = x"yes"])
AM_CONDITIONAL([USE_EXTERNAL_ASM], [test x"$enable_external_asm" = x"yes"])
AM_CONDITIONAL([USE_ASM_ARM], [test x"$set_asm" = x"arm"])
AM_CONDITIONAL([BUILD_WINDOWS], [test "$build_windows" = "yes"])
Expand All @@ -411,6 +421,7 @@ echo " module ecdh = $enable_module_ecdh"
echo " module recovery = $enable_module_recovery"
echo " module extrakeys = $enable_module_extrakeys"
echo " module schnorrsig = $enable_module_schnorrsig"
echo " module ellsq = $enable_module_ellsq"
echo
echo " asm = $set_asm"
echo " ecmult window size = $set_ecmult_window"
Expand Down
31 changes: 29 additions & 2 deletions doc/safegcd_implementation.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# The safegcd implementation in libsecp256k1 explained

This document explains the modular inverse implementation in the `src/modinv*.h` files. It is based
on the paper
This document explains the modular inverse and Jacobi symbol implementations in the `src/modinv*.h` files.
It is based on the paper
["Fast constant-time gcd computation and modular inversion"](https://gcd.cr.yp.to/papers.html#safegcd)
by Daniel J. Bernstein and Bo-Yin Yang. The references below are for the Date: 2019.04.13 version.

Expand Down Expand Up @@ -769,3 +769,30 @@ def modinv_var(M, Mi, x):
d, e = update_de(d, e, t, M, Mi)
return normalize(f, d, Mi)
```

## 8. From GCDs to Jacobi symbol

We can also use a similar approach to calculate Jacobi symbol *(x | M)* by keeping track of an extra variable *j*, for which at every step *(x | M) = j (g | f)*. As we update *f* and *g*, we make corresponding updates to *j* using [properties of the Jacobi symbol](https://en.wikipedia.org/wiki/Jacobi_symbol#Properties). In particular, we update *j* whenever we divide *g* by *2* or swap *f* and *g*; these updates depend only on the values of *f* and *g* modulo *4* or *8*, and can thus be applied very quickly. Overall, this calculation is slightly simpler than the one for modular inverse because we no longer need to keep track of *d* and *e*.

However, one difficulty of this approach is that the Jacobi symbol *(a | n)* is only defined for positive odd integers *n*, whereas in the original safegcd algorithm, *f, g* can take negative values. We resolve this by using the following modified steps:

```python
# Before
if delta > 0 and g & 1:
delta, f, g = 1 - delta, g, (g - f) // 2

# After
if delta > 0 and g & 1:
delta, f, g = 1 - delta, g, (g + f) // 2
```

The algorithm is still correct, since the changed divstep, called a "posdivstep" (see section 8.4 and E.5 in the paper) preserves *gcd(f, g)*. However, there's no proof that the modified algorithm will converge. The justification for posdivsteps is completely empirical: in practice, it appears that the vast majority of inputs converge to *f=g=gcd(f<sub>0</sub>, g<sub>0<sub>)* in a number of steps proportional to their logarithm.

Note that:
- We require inputs to satisfy *gcd(x, M) = 1*.
- We need to update the termination condition from *g=0* to *f=1*.
- We deal with the case where *g=0* on input specially.

We account for the possibility of nonconvergence by only performing a bounded number of posdivsteps, and then falling back to square-root based Jacobi calculation if a solution has not yet been found.

The optimizations in sections 3-7 above are described in the context of the original divsteps, but in the C implementation we also adapt most of them (not including "avoiding modulus operations", since it's not necessary to track *d, e*, and "constant-time operation", since we never calculate Jacobi symbols for secret data) to the posdivsteps version.
78 changes: 78 additions & 0 deletions include/secp256k1_ellsq.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
#ifndef SECP256K1_ELLSQ_H
#define SECP256K1_ELLSQ_H

#include "secp256k1.h"

#ifdef __cplusplus
extern "C" {
#endif

/* This module provides an implementation of the Elligator Squared encoding
* for secp256k1 public keys. Given a uniformly random public key, this
* produces a 64-byte encoding that is indistinguishable from uniformly
* random bytes.
*
* Elligator Squared is described in https://eprint.iacr.org/2014/043.pdf by
* Mehdi Tibouchi. The mapping function used is described in
* https://www.di.ens.fr/~fouque/pub/latincrypt12.pdf by Fouque and Tibouchi.
*
* Let f be the function from field elements to curve points, defined as
* follows:
* f(t):
* - Let c = 0xa2d2ba93507f1df233770c2a797962cc61f6d15da14ecd47d8d27ae1cd5f852
* - Let x1 = (c - 1)/2 - c*t^2 / (t^2 + 8) (mod p)
* - Let x2 = (-c - 1)/2 + c*t^2 / (t^2 + 8) (mod p)
* - Let x3 = 1 - (t^2 + 8)^2 / (3*t^2) (mod p)
* - Let x be the first of [x1,x2,x3] that is an X coordinate on the curve
* (at least one of them is, for any field element t).
* - Let y be the the corresponding Y coordinate to x, with the same parity
* as t (even if t is even, odd if t is odd).
* - Return the curve point with coordinates (x, y).
*
* Then an Elligator Squared encoding of P consists of the 32-byte big-endian
* encodings of field elements u1 and u2 concatenated, where f(u1)+f(u2) = P.
* The encoding algorithm is described in the paper, and effectively picks a
* uniformly random pair (u1,u2) among those which encode P.
*
* To make the encoding able to deal with all inputs, if f(u1)+f(u2) is the
* point at infinity, the decoding is defined to be f(u1) instead.
*/

/* Construct a 64-byte Elligator Squared encoding of a given pubkey.
*
* Returns: 1 when pubkey is valid.
* Args: ctx: pointer to a context object
* Out: ell64: pointer to a 64-byte array to be filled
* In: rnd32: pointer to 32 bytes of entropy (must be unpredictable)
* pubkey: a pointer to a secp256k1_pubkey containing an
* initialized public key
*
* This function runs in variable time.
*/
SECP256K1_API int secp256k1_ellsq_encode(
const secp256k1_context* ctx,
unsigned char *ell64,
const unsigned char *rnd32,
const secp256k1_pubkey *pubkey
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4);

/** Decode a 64-bytes Elligator Squared encoded public key.
*
* Returns: always 1
* Args: ctx: pointer to a context object
* Out: pubkey: pointer to a secp256k1_pubkey that will be filled
* In: ell64: pointer to a 64-byte array to decode
*
* This function runs in variable time.
*/
SECP256K1_API int secp256k1_ellsq_decode(
const secp256k1_context* ctx,
secp256k1_pubkey *pubkey,
const unsigned char *ell64
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);

#ifdef __cplusplus
}
#endif

#endif /* SECP256K1_ELLSQ_H */
9 changes: 9 additions & 0 deletions src/bench.c
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,10 @@ static void bench_sign_run(void* arg, int iters) {
# include "modules/schnorrsig/bench_impl.h"
#endif

#ifdef ENABLE_MODULE_ELLSQ
# include "modules/ellsq/bench_impl.h"
#endif

int main(int argc, char** argv) {
int i;
secp256k1_pubkey pubkey;
Expand Down Expand Up @@ -230,5 +234,10 @@ int main(int argc, char** argv) {
run_schnorrsig_bench(iters, argc, argv);
#endif

#ifdef ENABLE_MODULE_ELLSQ
/* Elligator squared signature benchmarks */
run_ellsq_bench(iters, argc, argv);
#endif

return 0;
}
12 changes: 12 additions & 0 deletions src/bench_internal.c
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,17 @@ void bench_field_sqrt(void* arg, int iters) {
CHECK(j <= iters);
}

void bench_field_jacobi_var(void* arg, int iters) {
int i, j = 0;
bench_inv *data = (bench_inv*)arg;

for (i = 0; i < iters; i++) {
j += secp256k1_fe_jacobi_var(&data->fe[0]);
secp256k1_fe_add(&data->fe[0], &data->fe[1]);
}
CHECK(j <= iters);
}

void bench_group_double_var(void* arg, int iters) {
int i;
bench_inv *data = (bench_inv*)arg;
Expand Down Expand Up @@ -379,6 +390,7 @@ int main(int argc, char **argv) {
if (d || have_flag(argc, argv, "field") || have_flag(argc, argv, "mul")) run_benchmark("field_mul", bench_field_mul, bench_setup, NULL, &data, 10, iters*10);
if (d || have_flag(argc, argv, "field") || have_flag(argc, argv, "inverse")) run_benchmark("field_inverse", bench_field_inverse, bench_setup, NULL, &data, 10, iters);
if (d || have_flag(argc, argv, "field") || have_flag(argc, argv, "inverse")) run_benchmark("field_inverse_var", bench_field_inverse_var, bench_setup, NULL, &data, 10, iters);
if (d || have_flag(argc, argv, "field") || have_flag(argc, argv, "jacobi")) run_benchmark("field_jacobi_var", bench_field_jacobi_var, bench_setup, NULL, &data, 10, iters);
if (d || have_flag(argc, argv, "field") || have_flag(argc, argv, "sqrt")) run_benchmark("field_sqrt", bench_field_sqrt, bench_setup, NULL, &data, 10, iters);

if (d || have_flag(argc, argv, "group") || have_flag(argc, argv, "double")) run_benchmark("group_double_var", bench_group_double_var, bench_setup, NULL, &data, 10, iters*10);
Expand Down
3 changes: 3 additions & 0 deletions src/field.h
Original file line number Diff line number Diff line change
Expand Up @@ -139,4 +139,7 @@ static void secp256k1_fe_half(secp256k1_fe *r);
* magnitude set to 'm' and is normalized if (and only if) 'm' is zero. */
static void secp256k1_fe_get_bounds(secp256k1_fe *r, int m);

/** Compute the Jacobi symbol of a / p. 0 if a=0; 1 if a square; -1 if a non-square. */
static int secp256k1_fe_jacobi_var(const secp256k1_fe *a);

#endif /* SECP256K1_FIELD_H */
28 changes: 28 additions & 0 deletions src/field_10x26_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -1364,4 +1364,32 @@ static void secp256k1_fe_inv_var(secp256k1_fe *r, const secp256k1_fe *x) {
VERIFY_CHECK(secp256k1_fe_normalizes_to_zero(r) == secp256k1_fe_normalizes_to_zero(&tmp));
}

static int secp256k1_fe_jacobi_var(const secp256k1_fe *x) {
secp256k1_fe tmp;
secp256k1_modinv32_signed30 s;
int ret;

tmp = *x;
secp256k1_fe_normalize_var(&tmp);
secp256k1_fe_to_signed30(&s, &tmp);
ret = secp256k1_jacobi32_maybe_var(&s, &secp256k1_const_modinfo_fe);
if (ret == -2) {
/* secp256k1_jacobi32_maybe_var failed to compute the Jacobi symbol. Fall back
* to computing a square root. This should be extremely rare with random
* input. */
secp256k1_fe dummy;
ret = 2*secp256k1_fe_sqrt(&dummy, &tmp) - 1;
#ifdef VERIFY
} else {
secp256k1_fe dummy;
if (secp256k1_fe_is_zero(&tmp)) {
VERIFY_CHECK(ret == 0);
} else {
VERIFY_CHECK(ret == 2*secp256k1_fe_sqrt(&dummy, &tmp) - 1);
}
#endif
}
return ret;
}

#endif /* SECP256K1_FIELD_REPR_IMPL_H */
28 changes: 28 additions & 0 deletions src/field_5x52_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -667,4 +667,32 @@ static void secp256k1_fe_inv_var(secp256k1_fe *r, const secp256k1_fe *x) {
#endif
}

static int secp256k1_fe_jacobi_var(const secp256k1_fe *x) {
secp256k1_fe tmp;
secp256k1_modinv64_signed62 s;
int ret;

tmp = *x;
secp256k1_fe_normalize_var(&tmp);
secp256k1_fe_to_signed62(&s, &tmp);
ret = secp256k1_jacobi64_maybe_var(&s, &secp256k1_const_modinfo_fe);
if (ret == -2) {
/* secp256k1_jacobi64_maybe_var failed to compute the Jacobi symbol. Fall back
* to computing a square root. This should be extremely rare with random
* input. */
secp256k1_fe dummy;
ret = 2*secp256k1_fe_sqrt(&dummy, &tmp) - 1;
#ifdef VERIFY
} else {
secp256k1_fe dummy;
if (secp256k1_fe_is_zero(&tmp)) {
VERIFY_CHECK(ret == 0);
} else {
VERIFY_CHECK(ret == 2*secp256k1_fe_sqrt(&dummy, &tmp) - 1);
}
#endif
}
return ret;
}

#endif /* SECP256K1_FIELD_REPR_IMPL_H */
4 changes: 4 additions & 0 deletions src/modinv32.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,8 @@ static void secp256k1_modinv32_var(secp256k1_modinv32_signed30 *x, const secp256
/* Same as secp256k1_modinv32_var, but constant time in x (not in the modulus). */
static void secp256k1_modinv32(secp256k1_modinv32_signed30 *x, const secp256k1_modinv32_modinfo *modinfo);

/* Compute the Jacobi symbol for (x | modinfo->modulus). Either x must be 0, or x must be coprime with
* modulus. All limbs of x must be non-negative. Returns -2 if the result cannot be computed. */
static int secp256k1_jacobi32_maybe_var(const secp256k1_modinv32_signed30 *x, const secp256k1_modinv32_modinfo *modinfo);

#endif /* SECP256K1_MODINV32_H */
Loading