![Pieter Wuille](/assets/img/avatar_default.png)
705ce7ed8c Merge bitcoin-core/secp256k1#1129: ElligatorSwift + integrated x-only DH 0702ecb061 Merge bitcoin-core/secp256k1#1338: Drop no longer needed `#include "../include/secp256k1.h"` 90e360acc2 Add doc/ellswift.md with ElligatorSwift explanation 4f091847c2 Add ellswift testing to CI 1bcea8c57f Add benchmarks for ellswift module 2d1d41acf8 Add ctime tests for ellswift module df633cdeba Add _prefix and _bip324 ellswift_xdh hash functions 9695deb351 Add tests for ellswift module c47917bbd6 Add ellswift module implementing ElligatorSwift 79e5b2a8b8 Add functions to test if X coordinate is valid a597a5a9ce Add benchmark for key generation 30574f22ea Merge bitcoin-core/secp256k1#1349: Normalize ge produced from secp256k1_pubkey_load 45c5ca7675 Merge bitcoin-core/secp256k1#1350: scalar: introduce and use `secp256k1_{read,write}_be64` helpers f1652528be Normalize ge produced from secp256k1_pubkey_load 7067ee54b4 tests: add tests for `secp256k1_{read,write}_be64` 740528caad scalar: use newly introduced `secp256k1_{read,write}_be64` helpers (4x64 impl.) 67214f5f7d Merge bitcoin-core/secp256k1#1339: scalar: refactor: use `secp256k1_{read,write}_be32` helpers cb1a59275c Merge bitcoin-core/secp256k1#1341: docs: correct `pubkey` param descriptions for `secp256k1_keypair_{xonly_,}pub` f3644287b1 docs: correct `pubkey` param descriptions for `secp256k1_keypair_{xonly_,}pub` 887183e7de scalar: use `secp256k1_{read,write}_be32` helpers (4x64 impl.) 52b84238de scalar: use `secp256k1_{read,write}_be32` helpers (8x32 impl.) e449af6872 Drop no longer needed `#include "../include/secp256k1.h"` 60556c9f49 Merge bitcoin-core/secp256k1#1337: ci: Fix error D8037 in `cl.exe` (attempt 2) db29bf220c ci: Remove quirk that runs dummy command after wineserver c7db4942b3 ci: Fix error D8037 in `cl.exe` 7dae115861 Revert "ci: Move wine prefix to /tmp to avoid error D8037 in cl.exe" bf29f8d0a6 Merge bitcoin-core/secp256k1#1334: fix input range comment for `secp256k1_fe_add_int` 605e07e365 fix input range comment for `secp256k1_fe_add_int` debf3e5c08 Merge bitcoin-core/secp256k1#1330: refactor: take use of `secp256k1_scalar_{zero,one}` constants d75dc59b58 Merge bitcoin-core/secp256k1#1333: test: Warn if both `VERIFY` and `COVERAGE` are defined ade5b36701 tests: add checks for scalar constants `secp256k1_scalar_{zero,one}` e83801f5db test: Warn if both `VERIFY` and `COVERAGE` are defined 654246c635 refactor: take use of `secp256k1_scalar_{zero,one}` constants 908e02d596 Merge bitcoin-core/secp256k1#1328: build: Bump MSVC warning level up to W3 1549db0ca5 build: Level up MSVC warnings 20a5da5fb1 Merge bitcoin-core/secp256k1#1310: Refine release process ad84603297 release process: clarify change log updates 6348bc7eee release process: fix process for maintenance release 79fa50b082 release process: mention targeted release schedule 165206789b release process: add sanity checks 09df0bfb23 Merge bitcoin-core/secp256k1#1327: ci: Move wine prefix to /tmp to avoid error D8037 in cl.exe 27504d5c94 ci: Move wine prefix to /tmp to avoid error D8037 in cl.exe d373a7215b Merge bitcoin-core/secp256k1#1316: Do not invoke fe_is_zero on failed set_b32_limit 6433175ffe Do not invoke fe_is_zero on failed set_b32_limit 5f7903c73c Merge bitcoin-core/secp256k1#1318: build: Enable -DVERIFY for precomputation binaries e9e4526a4e Merge bitcoin-core/secp256k1#1317: Make fe_cmov take max of magnitudes 5768b50229 build: Enable -DVERIFY for precomputation binaries 31b4bbee1e Make fe_cmov take max of magnitudes 83186db34a Merge bitcoin-core/secp256k1#1314: release cleanup: bump version after 0.3.2 95448ef2f8 release cleanup: bump version after 0.3.2 acf5c55ae6 Merge bitcoin-core/secp256k1#1312: release: Prepare for 0.3.2 d490ca2046 release: Prepare for 0.3.2 3e3d125b83 Merge bitcoin-core/secp256k1#1309: changelog: Catch up e8295d07ab Merge bitcoin-core/secp256k1#1311: Revert "Remove unused scratch space from API" 697e1ccf4a changelog: Catch up 3ad1027a40 Revert "Remove unused scratch space from API" 76b43f3443 changelog: Add entry for #1303 7d4f86d242 Merge bitcoin-core/secp256k1#1307: Mark more assembly outputs as early clobber b54a0672ef Merge bitcoin-core/secp256k1#1304: build: Rename arm to arm32 and check if it's really supported c6bb29b303 build: Rename `64bit` to `x86_64` 8c9ae37a5a Add release note 03246457a8 autotools: Add `SECP_ARM32_ASM_CHECK` macro ed4ba238e2 cmake: Add `check_arm32_assembly` function 350b4bd6e6 Mark stack variables as early clobber for technical correctness 0c729ba70d Bugfix: mark outputs as early clobber in scalar x86_64 asm 3353d3c753 Merge bitcoin-core/secp256k1#1207: Split fe_set_b32 into reducing and normalizing variants 5b32602295 Split fe_set_b32 into reducing and normalizing variants 006ddc1f42 Merge bitcoin-core/secp256k1#1306: build: Make tests work with external default callbacks 1907f0f166 build: Make tests work with external default callbacks fb3a806365 Merge bitcoin-core/secp256k1#1133: schnorrsig: Add test vectors for variable-length messages cd54ac7c1c schnorrsig: Improve docs of schnorrsig_sign_custom 28687b0312 schnorrsig: Add BIP340 varlen test vectors 97a98bed1e schnorrsig: Refactor test vector code to allow varlen messages ab5a917128 Merge bitcoin-core/secp256k1#1303: ct: Use more volatile 9eb6934f69 Merge bitcoin-core/secp256k1#1305: Remove unused scratch space from API 073d98a076 Merge bitcoin-core/secp256k1#1292: refactor: Make 64-bit shift explicit 17fa21733a ct: Be cautious and use volatile trick in more "conditional" paths 5fb336f9ce ct: Use volatile trick in scalar_cond_negate 712e7f8722 Remove unused scratch space from API 54d34b6c24 Merge bitcoin-core/secp256k1#1300: Avoid normalize conditional on VERIFY c63ec88ebf Merge bitcoin-core/secp256k1#1066: Abstract out and merge all the magnitude/normalized logic 7fc642fa25 Simplify secp256k1_fe_{impl_,}verify 4e176ad5b9 Abstract out verify logic for fe_is_square_var 4371f98346 Abstract out verify logic for fe_add_int 89e324c6b9 Abstract out verify logic for fe_half 283cd80ab4 Abstract out verify logic for fe_get_bounds d5aa2f0358 Abstract out verify logic for fe_inv{,_var} 3167646072 Abstract out verify logic for fe_from_storage 76d31e5047 Abstract out verify logic for fe_to_storage 1e6894bdd7 Abstract out verify logic for fe_cmov be82bd8e03 Improve comments/checks for fe_sqrt 6ab35082ef Abstract out verify logic for fe_sqr 4c25f6efbd Abstract out verify logic for fe_mul e179e651cb Abstract out verify logic for fe_add 7e7ad7ff57 Abstract out verify logic for fe_mul_int 65d82a3445 Abstract out verify logic for fe_negate 144670893e Abstract out verify logic for fe_get_b32 f7a7666aeb Abstract out verify logic for fe_set_b32 ce4d2093e8 Abstract out verify logic for fe_cmp_var 7d7d43c6dd Improve comments/check for fe_equal{,_var} c5e788d672 Abstract out verify logic for fe_is_odd d3f3fe8616 Abstract out verify logic for fe_is_zero c701d9a471 Abstract out verify logic for fe_clear 19a2bfeeea Abstract out verify logic for fe_set_int 864f9db491 Abstract out verify logic for fe_normalizes_to_zero{,_var} 6c31371120 Abstract out verify logic for fe_normalize_var e28b51f522 Abstract out verify logic for fe_normalize_weak b6b6f9cb97 Abstract out verify logic for fe_normalize 7fa5195559 Bugfix: correct SECP256K1_FE_CONST mag/norm fields e5cf4bf3ff build: Rename `arm` to `arm32` b29566c51b Merge magnitude/normalized fields, move/improve comments 97c63b9039 Avoid normalize conditional on VERIFY 341cc19726 Merge bitcoin-core/secp256k1#1299: Infinity handling: ecmult_const(infinity) works, and group verification bbc834467c Avoid secp256k1_ge_set_gej_zinv with uninitialized z 0a2e0b2ae4 Make secp256k1_{fe,ge,gej}_verify work as no-op if non-VERIFY f20266722a Add invariant checking to group elements a18821d5b1 Always initialize output coordinates in secp256k1_ge_set_gej 3086cb90ac Expose secp256k1_fe_verify to other modules a0e696fd4d Make secp256k1_ecmult_const handle infinity 24c768ae09 Merge bitcoin-core/secp256k1#1301: Avoid using bench_verify_data as bench_sign_data; merge them 2e65f1fdbc Avoid using bench_verify_data as bench_sign_data; merge them 1cf15ebd94 Merge bitcoin-core/secp256k1#1296: docs: complete interface description for `secp256k1_schnorrsig_sign_custom` 149c41cee1 docs: complete interface description for `secp256k1_schnorrsig_sign_custom` f30c74866b Merge bitcoin-core/secp256k1#1270: cmake: Fix library ABI versioning d1e48e5474 refactor: Make 64-bit shift explicit b2e29e43d0 ci: Treat all compiler warnings as errors in "Windows (VS 2022)" task 3c81838856 Merge bitcoin-core/secp256k1#1289: cmake: Use full signature of `add_test()` command 755629bc03 cmake: Use full signature of `add_test()` command bef448f9af cmake: Fix library ABI versioning 4b0f711d46 Merge bitcoin-core/secp256k1#1277: autotools: Clean up after adding Wycheproof 222ecaf661 Merge bitcoin-core/secp256k1#1284: cmake: Some improvements using `PROJECT_IS_TOP_LEVEL` variable 71f746c057 cmake: Include `include` directory for subtree builds 024a409484 Merge bitcoin-core/secp256k1#1240: cmake: Improve and document compiler flag checks a8d059f76c cmake, doc: Document compiler flags 6ece1507cb cmake, refactor: Rename `try_add_compile_option` to `try_append_cflags` 19516ed3e9 cmake: Use `add_compile_options()` in `try_add_compile_option()` 4b84f4bf0f Merge bitcoin-core/secp256k1#1239: cmake: Bugfix and other improvements after bumping CMake up to 3.13 596b336ff6 Merge bitcoin-core/secp256k1#1234: cmake: Add dev-mode 6b7e5b717d Merge bitcoin-core/secp256k1#1275: build: Fix C4005 "macro redefinition" MSVC warnings in examples 1c89536718 Merge bitcoin-core/secp256k1#1286: tests: remove extra semicolon in macro c4062d6b5d debug: move helper for printing buffers into util.h 7e977b3c50 autotools: Take VPATH builds into account when generating testvectors 2418d3260a autotools: Create src/wycheproof dir before creating file in it 8764034ed5 autotools: Make all "pregenerated" targets .PHONY e1b9ce8811 autotools: Use same conventions for all pregenerated files 3858bad2c6 tests: remove extra semicolon in macro 1f33bb2b1c Merge bitcoin-core/secp256k1#1205: field: Improve docs +tests of secp256k1_fe_set_b32 162da73e9a tests: Add debug helper for printing buffers e9fd3dff76 field: Improve docs and tests of secp256k1_fe_set_b32 f6bef03c0a Merge bitcoin-core/secp256k1#1283: Get rid of secp256k1_fe_const_b 5431b9decd cmake: Make `SECP256K1_INSTALL` default depend on `PROJECT_IS_TOP_LEVEL` 5ec1333d4f Merge bitcoin-core/secp256k1#1285: bench: Make sys/time.h a system include 68b16a1662 bench: Make sys/time.h a system include 162608cc98 cmake: Emulate `PROJECT_IS_TOP_LEVEL` for CMake<3.21 69e1ec0331 Get rid of secp256k1_fe_const_b ce5ba9e24d gitignore: Add CMakeUserPresets.json 0a446a312f cmake: Add dev-mode CMake preset a6f4bcf6e1 Merge bitcoin-core/secp256k1#1231: Move `SECP256K1_INLINE` macro definition out from `include/secp256k1.h` a273d74b2e cmake: Improve version comparison 6a58b483ef cmake: Use `if(... IN_LIST ...)` command 2445808c02 cmake: Use dedicated `GENERATOR_IS_MULTI_CONFIG` property 9f8703ef17 cmake: Use dedicated `CMAKE_HOST_APPLE` variable 8c2017035a cmake: Use recommended `add_compile_definitions` command 04d4cc071a cmake: Add `DESCRIPTION` and `HOMEPAGE_URL` options to `project` command 8a8b6536ef cmake: Use `SameMinorVersion` compatibility mode 5b0444a3b5 Merge bitcoin-core/secp256k1#1263: cmake: Make installation optional 47ac3d63cd cmake: Make installation optional 2e035af251 Merge bitcoin-core/secp256k1#1273: build: Make `SECP_VALGRIND_CHECK` preserve `CPPFLAGS` 5be353d658 Merge bitcoin-core/secp256k1#1279: tests: lint wycheproof's python script 08f4b1632d autotools: Move code around to tidy Makefile 04bf3f6778 Merge bitcoin-core/secp256k1#1230: Build: allow static or shared but not both 9ce9984f32 Merge bitcoin-core/secp256k1#1265: Remove bits argument from secp256k1_wnaf_const{_xonly} 566faa17d3 Merge bitcoin-core/secp256k1#1267: doc: clarify process for patch releases ef49a11d29 build: allow static or shared but not both 35ada3b954 tests: lint wycheproof's python script 529b54d922 autotools: Move Wycheproof header from EXTRA_DIST to noinst_HEADERS dc0657c762 build: Fix C4005 "macro redefinition" MSVC warnings in examples 1ecb94ebe9 build: Make `SECP_VALGRIND_CHECK` preserve `CPPFLAGS` 1b6fb5593c doc: clarify process for patch releases a575339c02 Remove bits argument from secp256k1_wnaf_const (always 256) 36b0adf1b9 build: remove warning until it's reproducible 8e142ca410 Move `SECP256K1_INLINE` macro definition out from `include/secp256k1.h` 77445898a5 Remove `SECP256K1_INLINE` usage from examples ca92a35d01 field: Simplify code in secp256k1_fe_set_b32 d93f62e369 field: Verify field element even after secp256k1_fe_set_b32 fails git-subtree-dir: src/secp256k1 git-subtree-split: 705ce7ed8c1557a31e1bfc99be06082c5098d9f5
27 KiB
ElligatorSwift for secp256k1 explained
In this document we explain how the ellswift
module implementation is related to the
construction in the
"SwiftEC: Shallue–van de Woestijne Indifferentiable Function To Elliptic Curves"
paper by Jorge Chávez-Saab, Francisco Rodríguez-Henríquez, and Mehdi Tibouchi.
- 1. Introduction
- 2. The decoding function
- 3. The encoding function
- 4. Encoding and decoding full (x, y) coordinates
1. Introduction
The ellswift
module effectively introduces a new 64-byte public key format, with the property
that (uniformly random) public keys can be encoded as 64-byte arrays which are computationally
indistinguishable from uniform byte arrays. The module provides functions to convert public keys
from and to this format, as well as convenience functions for key generation and ECDH that operate
directly on ellswift-encoded keys.
The encoding consists of the concatenation of two (32-byte big endian) encoded field elements u
and t.
Together they encode an x-coordinate on the curve x
, or (see further) a full point (x, y)
on
the curve.
Decoding consists of decoding the field elements u
and t
(values above the field size p
are taken modulo p
), and then evaluating F_u(t)
, which for every u
and t
results in a valid
x-coordinate on the curve. The functions F_u
will be defined in Section 2.
Encoding a given x
coordinate is conceptually done as follows:
- Loop:
- Pick a uniformly random field element
u.
- Compute the set
L = F_u^{-1}(x)
oft
values for whichF_u(t) = x
, which may have up to 8 elements. - With probability
1 - \dfrac{\\#L}{8}
, restart the loop. - Select a uniformly random
t \in L
and return(u, t).
- Pick a uniformly random field element
This is the ElligatorSwift algorithm, here given for just x-coordinates. An extension to full
(x, y)
points will be given in Section 4.
The algorithm finds a uniformly random (u, t)
among (almost all) those
for which F_u(t) = x.
Section 3.2 in the paper proves that the number of such encodings for
almost all x-coordinates on the curve (all but at most 39) is close to two times the field size
(specifically, it lies in the range 2q \pm (22\sqrt{q} + O(1))
, where q
is the size of the field).
2. The decoding function
First some definitions:
\mathbb{F}
is the finite field of sizeq
, of characteristic 5 or more, andq \equiv 1 \mod 3.
- For
secp256k1
,q = 2^{256} - 2^{32} - 977
, which satisfies that requirement.
- For
- Let
E
be the elliptic curve of points(x, y) \in \mathbb{F}^2
for whichy^2 = x^3 + ax + b
, witha
andb
public constants, for which\Delta_E = -16(4a^3 + 27b^2)
is a square, and at least one of(-b \pm \sqrt{-3 \Delta_E} / 36)/2
is a square. This implies that the order ofE
is either odd, or a multiple of 4. Ifa=0
, this condition is always fulfilled.- For
secp256k1
,a=0
andb=7.
- For
- Let the function
g(x) = x^3 + ax + b
, so theE
curve equation is alsoy^2 = g(x).
- Let the function
h(x) = 3x^3 + 4a.
- Define
V
as the set of solutions(x_1, x_2, x_3, z)
toz^2 = g(x_1)g(x_2)g(x_3).
- Define
S_u
as the set of solutions(X, Y)
toX^2 + h(u)Y^2 = -g(u)
andY \neq 0.
P_u
is a function from\mathbb{F}
toS_u
that will be defined below.\psi_u
is a function fromS_u
toV
that will be defined below.
Note: In the paper:
F_u
corresponds toF_{0,u}
there.P_u(t)
is calledP
there.- All
S_u
sets together correspond toS
there. - All
\psi_u
functions together (operating on elements ofS
) correspond to\psi
there.
Note that for V
, the left hand side of the equation z^2
is square, and thus the right
hand must also be square. As multiplying non-squares results in a square in \mathbb{F}
,
out of the three right-hand side factors an even number must be non-squares.
This implies that exactly 1 or exactly 3 out of
\\{g(x_1), g(x_2), g(x_3)\\}
must be square, and thus that for any (x_1,x_2,x_3,z) \in V
,
at least one of \\{x_1, x_2, x_3\\}
must be a valid x-coordinate on E.
There is one exception
to this, namely when z=0
, but even then one of the three values is a valid x-coordinate.
Define the decoding function F_u(t)
as:
- Let
(x_1, x_2, x_3, z) = \psi_u(P_u(t)).
- Return the first element
x
of(x_3, x_2, x_1)
which is a valid x-coordinate onE
(i.e.,g(x)
is square).
P_u(t) = (X(u, t), Y(u, t))
, where:
\begin{array}{lcl}
X(u, t) & = & \left\{\begin{array}{ll}
\dfrac{g(u) - t^2}{2t} & a = 0 \
\dfrac{g(u) + h(u)(Y_0(u) + X_0(u)t)^2}{X_0(u)(1 + h(u)t^2)} & a \neq 0
\end{array}\right. \
Y(u, t) & = & \left\{\begin{array}{ll}
\dfrac{X(u, t) + t}{u \sqrt{-3}} = \dfrac{g(u) + t^2}{2tu\sqrt{-3}} & a = 0 \
Y_0(u) + t(X(u, t) - X_0(u)) & a \neq 0
\end{array}\right.
\end{array}
P_u(t)
is defined:
- For
a=0
, unless:u = 0
ort = 0
(division by zero)g(u) = -t^2
(would giveY=0
).
- For
a \neq 0
, unless:X_0(u) = 0
orh(u)t^2 = -1
(division by zero)Y_0(u) (1 - h(u)t^2) = 2X_0(u)t
(would giveY=0
).
The functions X_0(u)
and Y_0(u)
are defined in Appendix A of the paper, and depend on various properties of E.
The function \psi_u
is the same for all curves: \psi_u(X, Y) = (x_1, x_2, x_3, z)
, where:
\begin{array}{lcl}
x_1 & = & \dfrac{X}{2Y} - \dfrac{u}{2} && \
x_2 & = & -\dfrac{X}{2Y} - \dfrac{u}{2} && \
x_3 & = & u + 4Y^2 && \
z & = & \dfrac{g(x_3)}{2Y}(u^2 + ux_1 + x_1^2 + a) = \dfrac{-g(u)g(x_3)}{8Y^3}
\end{array}
2.1 Decoding for secp256k1
Put together and specialized for a=0
curves, decoding (u, t)
to an x-coordinate is:
Define F_u(t)
as:
- Let
X = \dfrac{u^3 + b - t^2}{2t}.
- Let
Y = \dfrac{X + t}{u\sqrt{-3}}.
- Return the first
x
in(u + 4Y^2, \dfrac{-X}{2Y} - \dfrac{u}{2}, \dfrac{X}{2Y} - \dfrac{u}{2})
for whichg(x)
is square.
To make sure that every input decodes to a valid x-coordinate, we remap the inputs in case
P_u
is not defined (when u=0
, t=0
, or g(u) = -t^2
):
Define F_u(t)
as:
- Let
u'=u
ifu \neq 0
;1
otherwise (guaranteeingu' \neq 0
). - Let
t'=t
ift \neq 0
;1
otherwise (guaranteeingt' \neq 0
). - Let
t''=t'
ifg(u') \neq -t'^2
;2t'
otherwise (guaranteeingt'' \neq 0
andg(u') \neq -t''^2
). - Let
X = \dfrac{u'^3 + b - t''^2}{2t''}.
- Let
Y = \dfrac{X + t''}{u'\sqrt{-3}}.
- Return the first
x
in(u' + 4Y^2, \dfrac{-X}{2Y} - \dfrac{u'}{2}, \dfrac{X}{2Y} - \dfrac{u'}{2})
for whichx^3 + b
is square.
The choices here are not strictly necessary. Just returning a fixed constant in any of the undefined cases would suffice, but the approach here is simple enough and gives fairly uniform output even in these cases.
Note: in the paper these conditions result in \infty
as output, due to the use of projective coordinates there.
We wish to avoid the need for callers to deal with this special case.
This is implemented in secp256k1_ellswift_xswiftec_frac_var
(which decodes to an x-coordinate represented as a fraction), and
in secp256k1_ellswift_xswiftec_var
(which outputs the actual x-coordinate).
3. The encoding function
To implement F_u^{-1}(x)
, the function to find the set of inverses t
for which F_u(t) = x
, we have to reverse the process:
- Find all the
(X, Y) \in S_u
that could have given rise tox
, through thex_1
,x_2
, orx_3
formulas in\psi_u.
- Map those
(X, Y)
solutions tot
values usingP_u^{-1}(X, Y).
- For each of the found
t
values, verify thatF_u(t) = x.
- Return the remaining
t
values.
The function P_u^{-1}
, which finds t
given (X, Y) \in S_u
, is significantly simpler than P_u:
P_u^{-1}(X, Y) = \left\{\begin{array}{ll}
Yu\sqrt{-3} - X & a = 0 \
\dfrac{Y-Y_0(u)}{X-X_0(u)} & a \neq 0 \land X \neq X_0(u) \
\dfrac{-X_0(u)}{h(u)Y_0(u)} & a \neq 0 \land X = X_0(u) \land Y = Y_0(u)
\end{array}\right.
The third step above, verifying that F_u(t) = x
, is necessary because for the (X, Y)
values found through the x_1
and x_2
expressions,
it is possible that decoding through \psi_u(X, Y)
yields a valid x_3
on the curve, which would take precedence over the
x_1
or x_2
decoding. These (X, Y)
solutions must be rejected.
Since we know that exactly one or exactly three out of \\{x_1, x_2, x_3\\}
are valid x-coordinates for any t
,
the case where either x_1
or x_2
is valid and in addition also x_3
is valid must mean that all three are valid.
This means that instead of checking whether x_3
is on the curve, it is also possible to check whether the other one out of
x_1
and x_2
is on the curve. This is significantly simpler, as it turns out.
Observe that \psi_u
guarantees that x_1 + x_2 = -u.
So given either x = x_1
or x = x_2
, the other one of the two can be computed as
-u - x.
Thus, when encoding x
through the x_1
or x_2
expressions, one can simply check whether g(-u-x)
is a square,
and if so, not include the corresponding t
values in the returned set. As this does not need X
, Y
, or t
, this condition can be determined
before those values are computed.
It is not possible that an encoding found through the x_1
expression decodes to a different valid x-coordinate using x_2
(which would
take precedence), for the same reason: if both x_1
and x_2
decodings were valid, x_3
would be valid as well, and thus take
precedence over both. Because of this, the g(-u-x)
being square test for x_1
and x_2
is the only test necessary to guarantee the found t
values round-trip back to the input x
correctly. This is the reason for choosing the (x_3, x_2, x_1)
precedence order in the decoder;
any order which does not place x_3
first requires more complicated round-trip checks in the encoder.
3.1 Switching to v, w coordinates
Before working out the formulas for all this, we switch to different variables for S_u.
Let v = (X/Y - u)/2
, and
w = 2Y.
Or in the other direction, X = w(u/2 + v)
and Y = w/2:
S_u'
becomes the set of(v, w)
for whichw^2 (u^2 + uv + v^2 + a) = -g(u)
andw \neq 0.
- For
a=0
curves,P_u^{-1}
can be stated for(v,w)
asP_u^{'-1}(v, w) = w\left(\frac{\sqrt{-3}-1}{2}u - v\right).
\psi_u
can be stated for(v, w)
as\psi_u'(v, w) = (x_1, x_2, x_3, z)
, where
\begin{array}{lcl}
x_1 & = & v \
x_2 & = & -u - v \
x_3 & = & u + w^2 \
z & = & \dfrac{g(x_3)}{w}(u^2 + uv + v^2 + a) = \dfrac{-g(u)g(x_3)}{w^3}
\end{array}
We can now write the expressions for finding (v, w)
given x
explicitly, by solving each of the \\{x_1, x_2, x_3\\}
expressions for v
or w
, and using the S_u'
equation to find the other variable:
- Assuming
x = x_1
, we findv = x
andw = \pm\sqrt{-g(u)/(u^2 + uv + v^2 + a)}
(two solutions). - Assuming
x = x_2
, we findv = -u-x
andw = \pm\sqrt{-g(u)/(u^2 + uv + v^2 + a)}
(two solutions). - Assuming
x = x_3
, we findw = \pm\sqrt{x-u}
andv = -u/2 \pm \sqrt{-w^2(4g(u) + w^2h(u))}/(2w^2)
(four solutions).
3.2 Avoiding computing all inverses
The ElligatorSwift algorithm as stated in Section 1 requires the computation of L = F_u^{-1}(x)
(the
set of all t
such that (u, t)
decode to x
) in full. This is unnecessary.
Observe that the procedure of restarting with probability (1 - \frac{\\#L}{8})
and otherwise returning a
uniformly random element from L
is actually equivalent to always padding L
with \bot
values up to length 8,
picking a uniformly random element from that, restarting whenever \bot
is picked:
Define ElligatorSwift(x) as:
- Loop:
- Pick a uniformly random field element
u.
- Compute the set
L = F_u^{-1}(x).
- Let
T
be the 8-element vector consisting of the elements ofL
, plus8 - \\#L
times\\{\bot\\}.
- Select a uniformly random
t \in T.
- If
t \neq \bot
, return(u, t)
; restart loop otherwise.
- Pick a uniformly random field element
Now notice that the order of elements in T
does not matter, as all we do is pick a uniformly
random element in it, so we do not need to have all \bot
values at the end.
As we have 8 distinct formulas for finding (v, w)
(taking the variants due to \pm
into account),
we can associate every index in T
with exactly one of those formulas, making sure that:
- Formulas that yield no solutions (due to division by zero or non-existing square roots) or invalid solutions are made to return
\bot.
- For the
x_1
andx_2
cases, ifg(-u-x)
is a square,\bot
is returned instead (the round-trip check). - In case multiple formulas would return the same non-
\bot
result, all but one of those must be turned into\bot
to avoid biasing those.
The last condition above only occurs with negligible probability for cryptographically-sized curves, but is interesting to take into account as it allows exhaustive testing in small groups. See Section 3.4 for an analysis of all the negligible cases.
If we define T = (G_{0,u}(x), G_{1,u}(x), \ldots, G_{7,u}(x))
, with each G_{i,u}
matching one of the formulas,
the loop can be simplified to only compute one of the inverses instead of all of them:
Define ElligatorSwift(x) as:
- Loop:
- Pick a uniformly random field element
u.
- Pick a uniformly random integer
c
in[0,8).
- Let
t = G_{c,u}(x).
- If
t \neq \bot
, return(u, t)
; restart loop otherwise.
- Pick a uniformly random field element
This is implemented in secp256k1_ellswift_xelligatorswift_var
.
3.3 Finding the inverse
To implement G_{c,u}
, we map c=0
to the x_1
formula, c=1
to the x_2
formula, and c=2
and c=3
to the x_3
formula.
Those are then repeated as c=4
through c=7
for the other sign of w
(noting that in each formula, w
is a square root of some expression).
Ignoring the negligible cases, we get:
Define G_{c,u}(x)
as:
- If
c \in \\{0, 1, 4, 5\\}
(forx_1
andx_2
formulas):- If
g(-u-x)
is square, return\bot
(asx_3
would be valid and take precedence). - If
c \in \\{0, 4\\}
(thex_1
formula) letv = x
, otherwise letv = -u-x
(thex_2
formula) - Let
s = -g(u)/(u^2 + uv + v^2 + a)
(usings = w^2
in what follows).
- If
- Otherwise, when
c \in \\{2, 3, 6, 7\\}
(forx_3
formulas):- Let
s = x-u.
- Let
r = \sqrt{-s(4g(u) + sh(u))}.
- Let
v = (r/s - u)/2
ifc \in \\{3, 7\\}
;(-r/s - u)/2
otherwise.
- Let
- Let
w = \sqrt{s}.
- Depending on
c:
- If
c \in \\{0, 1, 2, 3\\}:
returnP_u^{'-1}(v, w).
- If
c \in \\{4, 5, 6, 7\\}:
returnP_u^{'-1}(v, -w).
- If
Whenever a square root of a non-square is taken, \bot
is returned; for both square roots this happens with roughly
50% on random inputs. Similarly, when a division by 0 would occur, \bot
is returned as well; this will only happen
with negligible probability. A division by 0 in the first branch in fact cannot occur at all, because u^2 + uv + v^2 + a = 0
implies g(-u-x) = g(x)
which would mean the g(-u-x)
is square condition has triggered
and \bot
would have been returned already.
Note: In the paper, the case
variable corresponds roughly to the c
above, but only takes on 4 possible values (1 to 4).
The conditional negation of w
at the end is done randomly, which is equivalent, but makes testing harder. We choose to
have the G_{c,u}
be deterministic, and capture all choices in c.
Now observe that the c \in \\{1, 5\\}
and c \in \\{3, 7\\}
conditions effectively perform the same v \rightarrow -u-v
transformation. Furthermore, that transformation has no effect on s
in the first branch
as u^2 + ux + x^2 + a = u^2 + u(-u-x) + (-u-x)^2 + a.
Thus we can extract it out and move it down:
Define G_{c,u}(x)
as:
- If
c \in \\{0, 1, 4, 5\\}:
- If
g(-u-x)
is square, return\bot.
- Let
s = -g(u)/(u^2 + ux + x^2 + a).
- Let
v = x.
- If
- Otherwise, when
c \in \\{2, 3, 6, 7\\}:
- Let
s = x-u.
- Let
r = \sqrt{-s(4g(u) + sh(u))}.
- Let
v = (r/s - u)/2.
- Let
- Let
w = \sqrt{s}.
- Depending on
c:
- If
c \in \\{0, 2\\}:
returnP_u^{'-1}(v, w).
- If
c \in \\{1, 3\\}:
returnP_u^{'-1}(-u-v, w).
- If
c \in \\{4, 6\\}:
returnP_u^{'-1}(v, -w).
- If
c \in \\{5, 7\\}:
returnP_u^{'-1}(-u-v, -w).
- If
This shows there will always be exactly 0, 4, or 8 t
values for a given (u, x)
input.
There can be 0, 1, or 2 (v, w)
pairs before invoking P_u^{'-1}
, and each results in 4 distinct t
values.
3.4 Dealing with special cases
As mentioned before there are a few cases to deal with which only happen in a negligibly small subset of inputs.
For cryptographically sized fields, if only random inputs are going to be considered, it is unnecessary to deal with these. Still, for completeness
we analyse them here. They generally fall into two categories: cases in which the encoder would produce t
values that
do not decode back to x
(or at least cannot guarantee that they do), and cases in which the encoder might produce the same
t
value for multiple c
inputs (thereby biasing that encoding):
- In the branch for
x_1
andx_2
(wherec \in \\{0, 1, 4, 5\\}
):- When
g(u) = 0
, we would haves=w=Y=0
, which is not onS_u.
This is only possible on even-ordered curves. Excluding this also removes the one condition under which the simplified check forx_3
on the curve fails (namely wheng(x_1)=g(x_2)=0
butg(x_3)
is not square). This does exclude some valid encodings: when bothg(u)=0
andu^2+ux+x^2+a=0
(also implyingg(x)=0
), theS_u'
equation degenerates to0 = 0
, and many validt
values may exist. Yet, these cannot be targeted uniformly by the encoder anyway as there will generally be more than 8. - When
g(x) = 0
, the samet
would be produced as in thex_3
branch (wherec \in \\{2, 3, 6, 7\\}
) which we give precedence as it can deal withg(u)=0
. This is again only possible on even-ordered curves.
- When
- In the branch for
x_3
(wherec \in \\{2, 3, 6, 7\\}
):- When
s=0
, a division by zero would occur. - When
v = -u-v
andc \in \\{3, 7\\}
, the samet
would be returned as in thec \in \\{2, 6\\}
cases. It is equivalent to checking whetherr=0
. This cannot occur in thex_1
orx_2
branches, as it would trigger theg(-u-x)
is square condition. A similar concern forw = -w
does not exist, asw=0
is already impossible in both branches: in the first it requiresg(u)=0
which is already outlawed on even-ordered curves and impossible on others; in the second it would trigger division by zero.
- When
- Curve-specific special cases also exist that need to be rejected, because they result in
(u,t)
which is invalid to the decoder, or because of division by zero in the encoder:- For
a=0
curves, whenu=0
or whent=0
. The latter can only be reached by the encoder wheng(u)=0
, which requires an even-ordered curve. - For
a \neq 0
curves, whenX_0(u)=0
, whenh(u)t^2 = -1
, or when2w(u + 2v) = 2X_0(u)
while also eitherw \neq 2Y_0(u)
orh(u)=0
.
- For
Define a version of G_{c,u}(x)
which deals with all these cases:
- If
a=0
andu=0
, return\bot.
- If
a \neq 0
andX_0(u)=0
, return\bot.
- If
c \in \\{0, 1, 4, 5\\}:
- If
g(u) = 0
org(x) = 0
, return\bot
(even curves only). - If
g(-u-x)
is square, return\bot.
- Let
s = -g(u)/(u^2 + ux + x^2 + a)
(cannot cause division by zero). - Let
v = x.
- If
- Otherwise, when
c \in \\{2, 3, 6, 7\\}:
- Let
s = x-u.
- Let
r = \sqrt{-s(4g(u) + sh(u))}
; return\bot
if not square. - If
c \in \\{3, 7\\}
andr=0
, return\bot.
- If
s = 0
, return\bot.
- Let
v = (r/s - u)/2.
- Let
- Let
w = \sqrt{s}
; return\bot
if not square. - If
a \neq 0
andw(u+2v) = 2X_0(u)
and eitherw \neq 2Y_0(u)
orh(u) = 0
, return\bot.
- Depending on
c:
- If
c \in \\{0, 2\\}
, lett = P_u^{'-1}(v, w).
- If
c \in \\{1, 3\\}
, lett = P_u^{'-1}(-u-v, w).
- If
c \in \\{4, 6\\}
, lett = P_u^{'-1}(v, -w).
- If
c \in \\{5, 7\\}
, lett = P_u^{'-1}(-u-v, -w).
- If
- If
a=0
andt=0
, return\bot
(even curves only). - If
a \neq 0
andh(u)t^2 = -1
, return\bot.
- Return
t.
Given any u
, using this algorithm over all x
and c
values, every t
value will be reached exactly once,
for an x
for which F_u(t) = x
holds, except for these cases that will not be reached:
- All cases where
P_u(t)
is not defined:- For
a=0
curves, whenu=0
,t=0
, org(u) = -t^2.
- For
a \neq 0
curves, whenh(u)t^2 = -1
,X_0(u) = 0
, orY_0(u) (1 - h(u) t^2) = 2X_0(u)t.
- For
- When
g(u)=0
, the potentially manyt
values that decode to anx
satisfyingg(x)=0
using thex_2
formula. These were excluded by theg(u)=0
condition in thec \in \\{0, 1, 4, 5\\}
branch.
These cases form a negligible subset of all (u, t)
for cryptographically sized curves.
3.5 Encoding for secp256k1
Specialized for odd-ordered a=0
curves:
Define G_{c,u}(x)
as:
- If
u=0
, return\bot.
- If
c \in \\{0, 1, 4, 5\\}:
- If
(-u-x)^3 + b
is square, return\bot
- Let
s = -(u^3 + b)/(u^2 + ux + x^2)
(cannot cause division by 0). - Let
v = x.
- If
- Otherwise, when
c \in \\{2, 3, 6, 7\\}:
- Let
s = x-u.
- Let
r = \sqrt{-s(4(u^3 + b) + 3su^2)}
; return\bot
if not square. - If
c \in \\{3, 7\\}
andr=0
, return\bot.
- If
s = 0
, return\bot.
- Let
v = (r/s - u)/2.
- Let
- Let
w = \sqrt{s}
; return\bot
if not square. - Depending on
c:
- If
c \in \\{0, 2\\}:
returnw(\frac{\sqrt{-3}-1}{2}u - v).
- If
c \in \\{1, 3\\}:
returnw(\frac{\sqrt{-3}+1}{2}u + v).
- If
c \in \\{4, 6\\}:
returnw(\frac{-\sqrt{-3}+1}{2}u + v).
- If
c \in \\{5, 7\\}:
returnw(\frac{-\sqrt{-3}-1}{2}u - v).
- If
This is implemented in secp256k1_ellswift_xswiftec_inv_var
.
And the x-only ElligatorSwift encoding algorithm is still:
Define ElligatorSwift(x) as:
- Loop:
- Pick a uniformly random field element
u.
- Pick a uniformly random integer
c
in[0,8).
- Let
t = G_{c,u}(x).
- If
t \neq \bot
, return(u, t)
; restart loop otherwise.
- Pick a uniformly random field element
Note that this logic does not take the remapped u=0
, t=0
, and g(u) = -t^2
cases into account; it just avoids them.
While it is not impossible to make the encoder target them, this would increase the maximum number of t
values for a given (u, x)
combination beyond 8, and thereby slow down the ElligatorSwift loop proportionally, for a negligible gain in uniformity.
4. Encoding and decoding full (x, y) coordinates
So far we have only addressed encoding and decoding x-coordinates, but in some cases an encoding
for full points with (x, y)
coordinates is desirable. It is possible to encode this information
in t
as well.
Note that for any (X, Y) \in S_u
, (\pm X, \pm Y)
are all on S_u.
Moreover, all of these are
mapped to the same x-coordinate. Negating X
or negating Y
just results in x_1
and x_2
being swapped, and does not affect x_3.
This will not change the outcome x-coordinate as the order
of x_1
and x_2
only matters if both were to be valid, and in that case x_3
would be used instead.
Still, these four (X, Y)
combinations all correspond to distinct t
values, so we can encode
the sign of the y-coordinate in the sign of X
or the sign of Y.
They correspond to the
four distinct P_u^{'-1}
calls in the definition of G_{u,c}.
Note: In the paper, the sign of the y coordinate is encoded in a separately-coded bit.
To encode the sign of y
in the sign of Y:
Define Decode(u, t) for full (x, y)
as:
- Let
(X, Y) = P_u(t).
- Let
x
be the first value in(u + 4Y^2, \frac{-X}{2Y} - \frac{u}{2}, \frac{X}{2Y} - \frac{u}{2})
for whichg(x)
is square. - Let
y = \sqrt{g(x)}.
- If
sign(y) = sign(Y)
, return(x, y)
; otherwise return(x, -y).
And encoding would be done using a G_{c,u}(x, y)
function defined as:
Define G_{c,u}(x, y)
as:
- If
c \in \\{0, 1\\}:
- If
g(u) = 0
org(x) = 0
, return\bot
(even curves only). - If
g(-u-x)
is square, return\bot.
- Let
s = -g(u)/(u^2 + ux + x^2 + a)
(cannot cause division by zero). - Let
v = x.
- If
- Otherwise, when
c \in \\{2, 3\\}:
- Let
s = x-u.
- Let
r = \sqrt{-s(4g(u) + sh(u))}
; return\bot
if not square. - If
c = 3
andr = 0
, return\bot.
- Let
v = (r/s - u)/2.
- Let
- Let
w = \sqrt{s}
; return\bot
if not square. - Let
w' = w
ifsign(w/2) = sign(y)
;-w
otherwise. - Depending on
c:
- If
c \in \\{0, 2\\}:
returnP_u^{'-1}(v, w').
- If
c \in \\{1, 3\\}:
returnP_u^{'-1}(-u-v, w').
- If
Note that c
now only ranges [0,4)
, as the sign of w'
is decided based on that of y
, rather than on c.
This change makes some valid encodings unreachable: when y = 0
and sign(Y) \neq sign(0)
.
In the above logic, sign
can be implemented in several ways, such as parity of the integer representation
of the input field element (for prime-sized fields) or the quadratic residuosity (for fields where
-1
is not square). The choice does not matter, as long as it only takes on two possible values, and for x \neq 0
it holds that sign(x) \neq sign(-x)
.
4.1 Full (x, y) coordinates for secp256k1
For a=0
curves, there is another option. Note that for those,
the P_u(t)
function translates negations of t
to negations of (both) X
and Y.
Thus, we can use sign(t)
to
encode the y-coordinate directly. Combined with the earlier remapping to guarantee all inputs land on the curve, we get
as decoder:
Define Decode(u, t) as:
- Let
u'=u
ifu \neq 0
;1
otherwise. - Let
t'=t
ift \neq 0
;1
otherwise. - Let
t''=t'
ifu'^3 + b + t'^2 \neq 0
;2t'
otherwise. - Let
X = \dfrac{u'^3 + b - t''^2}{2t''}.
- Let
Y = \dfrac{X + t''}{u'\sqrt{-3}}.
- Let
x
be the first element of(u' + 4Y^2, \frac{-X}{2Y} - \frac{u'}{2}, \frac{X}{2Y} - \frac{u'}{2})
for whichg(x)
is square. - Let
y = \sqrt{g(x)}.
- Return
(x, y)
ifsign(y) = sign(t)
;(x, -y)
otherwise.
This is implemented in secp256k1_ellswift_swiftec_var
. The used sign(x)
function is the parity of x
when represented as in integer in [0,q).
The corresponding encoder would invoke the x-only one, but negating the output t
if sign(t) \neq sign(y).
This is implemented in secp256k1_ellswift_elligatorswift_var
.
Note that this is only intended for encoding points where both the x-coordinate and y-coordinate are unpredictable. When encoding x-only points
where the y-coordinate is implicitly even (or implicitly square, or implicitly in [0,q/2]
), the encoder in
Section 3.5 must be used, or a bias is reintroduced that undoes all the benefit of using ElligatorSwift
in the first place.