mirror of
https://github.com/torproject/torspec.git
synced 2024-12-13 05:05:41 +00:00
765 lines
31 KiB
Plaintext
765 lines
31 KiB
Plaintext
Filename: 270-newhope-hybrid-handshake.txt
|
||
Title: RebelAlliance: A Post-Quantum Secure Hybrid Handshake Based on NewHope
|
||
Author: Isis Lovecruft, Peter Schwabe
|
||
Created: 16 Apr 2016
|
||
Updated: 22 Jul 2016
|
||
Status: Obsolete
|
||
Depends: prop#220 prop#249 prop#264 prop#270
|
||
|
||
§0. Introduction
|
||
|
||
RebelAlliance is a post-quantum secure hybrid handshake, comprised of an
|
||
alliance between the X25519 and NewHope key exchanges.
|
||
|
||
NewHope is a post-quantum-secure lattice-based key-exchange protocol based
|
||
on the ring-learning-with-errors (Ring-LWE) problem. We propose a hybrid
|
||
handshake for Tor, based on a combination of Tor's current NTor handshake
|
||
and a shared key derived through a NewHope ephemeral key exchange.
|
||
|
||
For further details on the NewHope key exchange, the reader is referred to
|
||
"Post-quantum key exchange - a new hope" by Alkim, Ducas, Pöppelmann, and
|
||
Schwabe [0][1].
|
||
|
||
For the purposes of brevity, we consider that NTor is currently the only
|
||
handshake protocol in Tor; the older TAP protocol is ignored completely, due
|
||
to the fact that it is currently deprecated and nearly entirely unused.
|
||
|
||
|
||
§1. Motivation
|
||
|
||
An attacker currently monitoring and storing circuit-layer NTor handshakes
|
||
who later has the ability to run Shor's algorithm on a quantum computer will
|
||
be able to break Tor's current handshake protocol and decrypt previous
|
||
communications.
|
||
|
||
It is unclear if and when such attackers equipped with large quantum
|
||
computers will exist, but various estimates by researchers in quantum
|
||
physics and quantum engineering give estimates of only 1 to 2 decades.
|
||
Clearly, the security requirements of many Tor users include secrecy of
|
||
their messages beyond this time span, which means that Tor needs to update
|
||
the key exchange to protect against such attackers as soon as possible.
|
||
|
||
|
||
§2. Design
|
||
|
||
An initiator and responder, in parallel, conduct two handshakes:
|
||
|
||
- An X25519 key exchange, as described in the description of the NTor
|
||
handshake in Tor proposal #216.
|
||
- A NewHope key exchange.
|
||
|
||
The shared keys derived from these two handshakes are then concatenated and
|
||
used as input to the SHAKE-256 extendable output function (XOF), as described
|
||
in FIPS-PUB-202 [2], in order to produce a shared key of the desired length.
|
||
The testvectors in §C assume that this key has a length of 32 bytes, but the
|
||
use of a XOF allows arbitrary lengths to easily support future updates of
|
||
the symmetric primitives using the key. See also §3.3.1.
|
||
|
||
|
||
§3. Specification
|
||
|
||
§3.1. Notation
|
||
|
||
Let `a || b` be the concatenation of a with b.
|
||
|
||
Let `a^b` denote the exponentiation of a to the bth power.
|
||
|
||
Let `a == b` denote the equality of a with b, and vice versa.
|
||
|
||
Let `a := b` be the assignment of the value of b to the variable a.
|
||
|
||
Let `H(x)` be 32-bytes of output of the SHAKE-256 XOF (as described in
|
||
FIPS-PUB-202) applied to message x.
|
||
|
||
Let X25519 refer to the curve25519-based key agreement protocol described
|
||
in RFC7748 §6.1. [3]
|
||
|
||
Let `EXP(a, b) == X25519(., b, a)` with `g == 9`. Let X25519_KEYGEN() do
|
||
the appropriate manipulations when generating the secret key (clearing the
|
||
low bits, twidding the high bits). Additionally, EXP() MUST include the
|
||
check for all-zero output due to the input point being of small
|
||
order (cf. RFC7748 §6).
|
||
|
||
Let `X25519_KEYID(B) == B` where B is a valid X25519 public key.
|
||
|
||
When representing an element of the Curve25519 subgroup as a byte string,
|
||
use the standard (32-byte, little-endian, x-coordinate-only) representation
|
||
for Curve25519 points.
|
||
|
||
Let `ID` be a router's identity key taken from the router microdescriptor.
|
||
In the case for relays possessing Ed25519 identity keys (cf. Tor proposal
|
||
#220), this is a 32-byte string representing the public Ed25519 identity key.
|
||
For backwards and forwards compatibility with routers which do not possess
|
||
Ed25519 identity keys, this is a 32-byte string created via the output of
|
||
H(ID).
|
||
|
||
We refer to the router as the handshake "responder", and the client (which
|
||
may be an OR or an OP) as the "initiator".
|
||
|
||
|
||
ID_LENGTH [32 bytes]
|
||
H_LENGTH [32 bytes]
|
||
G_LENGTH [32 bytes]
|
||
|
||
PROTOID := "pqtor-x25519-newhope-shake256-1"
|
||
T_MAC := PROTOID || ":mac"
|
||
T_KEY := PROTOID || ":key_extract"
|
||
T_VERIFY := PROTOID || ":verify"
|
||
|
||
(X25519_SK, X25519_PK) := X25519_KEYGEN()
|
||
|
||
|
||
§3.2. Protocol
|
||
|
||
========================================================================================
|
||
| |
|
||
| Fig. 1: The NewHope-X25519 Hybrid Handshake. |
|
||
| |
|
||
| Before the handshake the Initiator is assumed to know Z, a public X25519 key for |
|
||
| the Responder, as well as the Responder's ID. |
|
||
----------------------------------------------------------------------------------------
|
||
| |
|
||
| Initiator Responder |
|
||
| |
|
||
| SEED := H(randombytes(32)) |
|
||
| x, X := X25519_KEYGEN() |
|
||
| a, A := NEWHOPE_KEYGEN(SEED) |
|
||
| CLIENT_HDATA := ID || Z || X || A |
|
||
| |
|
||
| --- CLIENT_HDATA ---> |
|
||
| |
|
||
| y, Y := X25519_KEYGEN() |
|
||
| NTOR_KEY, AUTH := NTOR_SHAREDB(X,y,Y,z,Z,ID,B) |
|
||
| M, NEWHOPE_KEY := NEWHOPE_SHAREDB(A) |
|
||
| SERVER_HDATA := Y || AUTH || M |
|
||
| sk := SHAKE-256(NTOR_KEY || NEWHOPE_KEY) |
|
||
| |
|
||
| <-- SERVER_HDATA ---- |
|
||
| |
|
||
| NTOR_KEY := NTOR_SHAREDA(x, X, Y, Z, ID, AUTH) |
|
||
| NEWHOPE_KEY := NEWHOPE_SHAREDA(M, a) |
|
||
| sk := SHAKE-256(NTOR_KEY || NEWHOPE_KEY) |
|
||
| |
|
||
========================================================================================
|
||
|
||
|
||
§3.2.1. The NTor Handshake
|
||
|
||
§3.2.1.1. Prologue
|
||
|
||
Take a router with identity ID. As setup, the router generates a secret key z,
|
||
and a public onion key Z with:
|
||
|
||
z, Z := X25519_KEYGEN()
|
||
|
||
The router publishes Z in its server descriptor in the "ntor-onion-key" entry.
|
||
Henceforward, we refer to this router as the "responder".
|
||
|
||
|
||
§3.2.1.2. Initiator
|
||
|
||
To send a create cell, the initiator generates a keypair:
|
||
|
||
x, X := X25519_KEYGEN()
|
||
|
||
and creates the NTor portion of a CREATE2V cell's HDATA section:
|
||
|
||
CLIENT_NTOR := ID || Z || X [96 bytes]
|
||
|
||
The initiator includes the responder's ID and Z in the CLIENT_NTOR so that, in
|
||
the event the responder OR has recently rotated keys, the responder can
|
||
determine which keypair to use.
|
||
|
||
The initiator then concatenates CLIENT_NTOR with CLIENT_NEWHOPE (see §3.2.2),
|
||
to create CLIENT_HDATA, and creates and sends a CREATE2V cell (see §A.1)
|
||
to the responder.
|
||
|
||
CLIENT_NEWHOPE [1824 bytes] (see §3.2.2)
|
||
CLIENT_HDATA := CLIENT_NTOR || CLIENT_NEWHOPE [1920 bytes]
|
||
|
||
If the responder does not respond with a CREATED2V cell, the initiator SHOULD
|
||
NOT attempt to extend the circuit through the responder by sending fragmented
|
||
EXTEND2 cells, since the responder's lack of support for CREATE2V cells is
|
||
assumed to imply the responder also lacks support for fragmented EXTEND2
|
||
cells. Alternatively, for initiators with a sufficiently late consensus
|
||
method, the initiator MUST check that "proto" line in the responder's
|
||
descriptor (cf. Tor proposal #264) advertises support for the "Relay"
|
||
subprotocol version 3 (see §5).
|
||
|
||
|
||
§3.2.1.3. Responder
|
||
|
||
The responder generates a keypair of y, Y = X25519_KEYGEN(), and does
|
||
NTOR_SHAREDB() as follows:
|
||
|
||
(NTOR_KEY, AUTH) ← NTOR_SHAREDB(X, y, Y, z, Z, ID, B):
|
||
secret_input := EXP(X, y) || EXP(X, z) || ID || B || Z || Y || PROTOID
|
||
NTOR_KEY := H(secret_input, T_KEY)
|
||
verify := H(secret_input, T_VERIFY)
|
||
auth_input := verify || ID || Z || Y || X || PROTOID || "Server"
|
||
AUTH := H(auth_input, T_MAC)
|
||
|
||
The responder sends a CREATED2V cell containing:
|
||
|
||
SERVER_NTOR := Y || AUTH [64 bytes]
|
||
SERVER_NEWHOPE [2048 bytes] (see §3.2.2)
|
||
SERVER_HDATA := SERVER_NTOR || SERVER_NEWHOPE [2112 bytes]
|
||
|
||
and sends this to the initiator.
|
||
|
||
|
||
§3.2.1.4. Finalisation
|
||
|
||
The initiator then checks Y is in G^* [see NOTE below], and does
|
||
NTOR_SHAREDA() as follows:
|
||
|
||
(NTOR_KEY) ← NTOR_SHAREDA(x, X, Y, Z, ID, AUTH)
|
||
secret_input := EXP(Y, x) || EXP(Z, x) || ID || Z || X || Y || PROTOID
|
||
NTOR_KEY := H(secret_input, T_KEY)
|
||
verify := H(secret_input, T_VERIFY)
|
||
auth_input := verify || ID || Z || Y || X || PROTOID || "Server"
|
||
if AUTH == H(auth_input, T_MAC)
|
||
return NTOR_KEY
|
||
|
||
Both parties now have a shared value for NTOR_KEY. They expand this into
|
||
the keys needed for the Tor relay protocol.
|
||
|
||
[XXX We think we want to omit the final hashing in the production of NTOR_KEY
|
||
here, and instead put all the inputs through SHAKE-256. --isis, peter]
|
||
|
||
[XXX We probably want to remove ID and B from the input to the shared key
|
||
material, since they serve for authentication but, as pre-established
|
||
"prologue" material to the handshake, they should not be used in attempts to
|
||
strengthen the cryptographic suitability of the shared key. Also, their
|
||
inclusion is implicit in the DH exponentiations. I should probably ask Ian
|
||
about the reasoning for the original design choice. --isis]
|
||
|
||
|
||
§3.2.2. The NewHope Handshake
|
||
|
||
§3.2.2.1. Parameters & Mathematical Structures
|
||
|
||
Let ℤ be the ring of rational integers. Let ℤq, for q ≥ 1, denote the quotient
|
||
ring ℤ/qℤ. We define R = ℤ[X]/((X^n)+1) as the ring of integer polynomials
|
||
modulo ((X^n)+1), and Rq = ℤq[X]/((X^n)+1) as the ring of integer polynomials
|
||
modulo ((X^n)+1) where each coefficient is reduced modulo q. When we refer to
|
||
a polynomial, we mean an element of Rq.
|
||
|
||
n := 1024
|
||
q := 12289
|
||
|
||
SEED [32 Bytes]
|
||
NEWHOPE_POLY [1792 Bytes]
|
||
NEWHOPE_REC [256 Bytes]
|
||
NEWHOPE_KEY [32 Bytes]
|
||
|
||
NEWHOPE_MSGA := (NEWHOPE_POLY || SEED)
|
||
NEWHOPE_MSGB := (NEWHOPE_POLY || NEWHOPE_REC)
|
||
|
||
|
||
§3.2.2.2. High-level Description of Newhope API Functions
|
||
|
||
For a description of internal functions, see §B.
|
||
|
||
(NEWHOPE_POLY, NEWHOPE_MSGA) ← NEWHOPE_KEYGEN(SEED):
|
||
â := gen_a(seed)
|
||
s := poly_getnoise()
|
||
e := poly_getnoise()
|
||
ŝ := poly_ntt(s)
|
||
ê := poly_ntt(e)
|
||
b̂ := pointwise(â, ŝ) + ê
|
||
sp := poly_tobytes(ŝ)
|
||
bp := poly_tobytes(b̂)
|
||
return (sp, (bp || seed))
|
||
|
||
(NEWHOPE_MSGB, NEWHOPE_KEY) ← NEWHOPE_SHAREDB(NEWHOPE_MSGA):
|
||
s' := poly_getnoise()
|
||
e' := poly_getnoise()
|
||
e" := poly_getnoise()
|
||
b̂ := poly_frombytes(bp)
|
||
â := gen_a(seed)
|
||
ŝ' := poly_ntt(s')
|
||
ê' := poly_ntt(e')
|
||
û := poly_pointwise(â, ŝ') + ê'
|
||
v := poly_invntt(poly_pointwise(b̂,ŝ')) + e"
|
||
r := helprec(v)
|
||
up := poly_tobytes(û)
|
||
k := rec(v, r)
|
||
return ((up || r), k)
|
||
|
||
NEWHOPE_KEY ← NEWHOPE_SHAREDA(NEWHOPE_MSGB, NEWHOPE_POLY):
|
||
û := poly_frombytes(up)
|
||
ŝ := poly_frombytes(sp)
|
||
v' := poly_invntt(poly_pointwise(û, ŝ))
|
||
k := rec(v', r)
|
||
return k
|
||
|
||
When a client uses a SEED within a CREATE2V cell, the client SHOULD NOT use
|
||
that SEED in any other CREATE2V or EXTEND2 cells. See §4 for further
|
||
discussion.
|
||
|
||
|
||
§3.3. Key Expansion
|
||
|
||
The client and server derive a shared key, SHARED, by:
|
||
|
||
HKDFID := "THESE ARENT THE DROIDS YOURE LOOKING FOR"
|
||
SHARED := SHAKE_256(HKDFID || NTorKey || NewHopeKey)
|
||
|
||
|
||
§3.3.1. Note on the Design Choice
|
||
|
||
The reader may wonder why one would use SHAKE-256 to produce a 256-bit
|
||
output, since the security strength in bits for SHAKE-256 is min(d/2,256)
|
||
for collision resistance and min(d,256) for first- and second-order
|
||
preimages, where d is the output length.
|
||
|
||
The reasoning is that we should be aiming for 256-bit security for all of
|
||
our symmetric cryptography. One could then argue that we should just use
|
||
SHA3-256 for the KDF. We choose SHAKE-256 instead in order to provide an
|
||
easy way to derive longer shared secrets in the future without requiring a
|
||
new handshake. The construction is odd, but the future is bright.
|
||
As we are already using SHAKE-256 for the 32-byte output hash, we are also
|
||
using it for all other 32-byte hashes involved in the protocol. Note that
|
||
the only difference between SHA3-256 and SHAKE-256 with 32-byte output is
|
||
one domain-separation byte.
|
||
|
||
[XXX why would you want 256-bit security for the symmetric side? Are you
|
||
talking pre- or post-quantum security? --peter]
|
||
|
||
|
||
§4. Security & Anonymity Implications
|
||
|
||
This handshake protocol is one-way authenticated. That is, the server is
|
||
authenticated, while the client remains anonymous.
|
||
|
||
The client MUST NOT cache and reuse SEED. Doing so gives non-trivial
|
||
adversarial advantages w.r.t. all-for-the-price-of-one attacks during the
|
||
caching period. More importantly, if the SEED used to generate NEWHOPE_MSGA
|
||
is reused for handshakes along the same circuit or multiple different
|
||
circuits, an adversary conducting a sybil attack somewhere along the path(s)
|
||
will be able to correlate the identity of the client across circuits or
|
||
hops.
|
||
|
||
|
||
§5. Compatibility
|
||
|
||
Because our proposal requires both the client and server to send more than
|
||
the 505 bytes possible within a CREATE2 cell's HDATA section, it depends
|
||
upon the implementation of a mechanism for allowing larger CREATE cells
|
||
(cf. Tor proposal #249).
|
||
|
||
We reserve the following handshake type for use in CREATE2V/CREATED2V and
|
||
EXTEND2V/EXTENDED2V cells:
|
||
|
||
0x0003 [NEWHOPE + X25519 HYBRID HANDSHAKE]
|
||
|
||
We introduce a new sub-protocol number, "Relay=3", (cf. Tor proposal #264
|
||
§5.3) to signify support this handshake, and hence for the CREATE2V and
|
||
fragmented EXTEND2 cells which it requires.
|
||
|
||
There are no additional entries or changes required within either router
|
||
descriptors or microdescriptors to support this handshake method, due to the
|
||
NewHope keys being ephemeral and derived on-the-fly, and due to the NTor X25519
|
||
public keys already being included within the "ntor-onion-key" entry.
|
||
|
||
Add a "UseNewHopeKEX" configuration option and a corresponding consensus
|
||
parameter to control whether clients prefer using this NewHope hybrid
|
||
handshake or some previous handshake protocol. If the configuration option
|
||
is "auto", clients SHOULD obey the consensus parameter. The default
|
||
configuration SHOULD be "auto" and the consensus value SHOULD initially be "0".
|
||
|
||
|
||
§6. Implementation
|
||
|
||
The paper by Alkim, Ducas, Pöppelmann and Schwabe describes two software
|
||
implementations of NewHope, one C reference implementation and an optimized
|
||
implementation using AVX2 vector instructions. Those implementations are
|
||
available at [1].
|
||
|
||
Additionally, there are implementations in Go by Yawning Angel, available
|
||
from [4] and in Rust by Isis Lovecruft, available from [5].
|
||
|
||
The software used to generate the test vectors in §C is based on the C
|
||
reference implementation and available from:
|
||
|
||
https://code.ciph.re/isis/newhope-tor-testvectors
|
||
https://github.com/isislovecruft/newhope-tor-testvectors
|
||
|
||
|
||
§7. Performance & Scalability
|
||
|
||
The computationally expensive part in the current NTor handshake is the
|
||
X25519 key-pair generation and the X25519 shared-key computation. The
|
||
current implementation in Tor is a wrapper to support various highly optimized
|
||
implementations on different architectures. On Intel Haswell processors, the
|
||
fastest implementation of X25519, as reported by the eBACS benchmarking
|
||
project [6], takes 169920 cycles for key-pair generation and 161648 cycles
|
||
for shared-key computation; these add up to a total of 331568 cycles on each
|
||
side (initiator and responder).
|
||
|
||
The C reference implementation of NewHope, also benchmarked on Intel
|
||
Haswell, takes 358234 cycles for the initiator and 402058 cycles for the
|
||
Responder. The core computation of the proposed combination of NewHope and
|
||
X25519 will thus mean a slowdown of about a factor of 2.1 for the Initiator
|
||
and a slowdown by a factor of 2.2 for the Responder compared to the current
|
||
NTor handshake. These numbers assume a fully optimized implementation of the
|
||
NTor handshake and a C reference implementation of NewHope. With optimized
|
||
implementations of NewHope, such as the one for Intel Haswell described in
|
||
[0], the computational slowdown will be considerably smaller than a factor
|
||
of 2.
|
||
|
||
|
||
§8. References
|
||
|
||
[0]: https://cryptojedi.org/papers/newhope-20160328.pdf
|
||
[1]: https://cryptojedi.org/crypto/#newhope
|
||
[2]: http://www.nist.gov/customcf/get_pdf.cfm?pub_id=919061
|
||
[3]: https://tools.ietf.org/html/rfc7748#section-6.1
|
||
[4]: https://github.com/Yawning/newhope
|
||
[5]: https://code.ciph.re/isis/newhopers
|
||
[6]: http://bench.cr.yp.to
|
||
|
||
|
||
§A. Cell Formats
|
||
|
||
§A.1. CREATE2V Cells
|
||
|
||
The client portion of the handshake should send CLIENT_HDATA, formatted
|
||
into a CREATE2V cell as follows:
|
||
|
||
CREATE2V { [2114 bytes]
|
||
HTYPE := 0x0003 [2 bytes]
|
||
HLEN := 0x0780 [2 bytes]
|
||
HDATA := CLIENT_HDATA [1920 bytes]
|
||
IGNORED := 0x00 [194 bytes]
|
||
}
|
||
|
||
[XXX do we really want to pad with IGNORED to make CLIENT_HDATA the
|
||
same number of bytes as SERVER_HDATA? --isis]
|
||
|
||
§A.2. CREATED2V Cells
|
||
|
||
The server responds to the client's CREATE2V cell with SERVER_HDATA,
|
||
formatted into a CREATED2V cell as follows:
|
||
|
||
CREATED2V { [2114 bytes]
|
||
HLEN := 0x0800 [2 bytes]
|
||
HDATA := SERVER_HDATA [2112 bytes]
|
||
IGNORED := 0x00 [0 bytes]
|
||
}
|
||
|
||
§A.3. Fragmented EXTEND2 Cells
|
||
|
||
When the client wishes to extend a circuit, the client should fragment
|
||
CLIENT_HDATA into four EXTEND2 cells:
|
||
|
||
EXTEND2 {
|
||
NSPEC := 0x02 { [1 byte]
|
||
LINK_ID_SERVER [22 bytes] XXX
|
||
LINK_ADDRESS_SERVER [8 bytes] XXX
|
||
}
|
||
HTYPE := 0x0003 [2 bytes]
|
||
HLEN := 0x0780 [2 bytes]
|
||
HDATA := CLIENT_HDATA[0,461] [462 bytes]
|
||
}
|
||
EXTEND2 {
|
||
NSPEC := 0x00 [1 byte]
|
||
HTYPE := 0xFFFF [2 bytes]
|
||
HLEN := 0x0000 [2 bytes]
|
||
HDATA := CLIENT_HDATA[462,954] [492 bytes]
|
||
}
|
||
EXTEND2 {
|
||
NSPEC := 0x00 [1 byte]
|
||
HTYPE := 0xFFFF [2 bytes]
|
||
HLEN := 0x0000 [2 bytes]
|
||
HDATA := CLIENT_HDATA[955,1447] [492 bytes]
|
||
}
|
||
EXTEND2 {
|
||
NSPEC := 0x00 [1 byte]
|
||
HTYPE := 0xFFFF [2 bytes]
|
||
HLEN := 0x0000 [2 bytes]
|
||
HDATA := CLIENT_HDATA[1448,1919] || 0x00[20] [492 bytes]
|
||
}
|
||
EXTEND2 {
|
||
NSPEC := 0x00 [1 byte]
|
||
HTYPE := 0xFFFF [2 bytes]
|
||
HLEN := 0x0000 [2 bytes]
|
||
HDATA := 0x00[172] [172 bytes]
|
||
}
|
||
|
||
The client sends this to the server to extend the circuit from, and that
|
||
server should format the fragmented EXTEND2 cells into a CREATE2V cell, as
|
||
described in §A.1.
|
||
|
||
§A.4. Fragmented EXTENDED2 Cells
|
||
|
||
EXTENDED2 {
|
||
NSPEC := 0x02 { [1 byte]
|
||
LINK_ID_SERVER [22 bytes] XXX
|
||
LINK_ADDRESS_SERVER [8 bytes] XXX
|
||
}
|
||
HTYPE := 0x0003 [2 bytes]
|
||
HLEN := 0x0800 [2 bytes]
|
||
HDATA := SERVER_HDATA[0,461] [462 bytes]
|
||
}
|
||
EXTENDED2 {
|
||
NSPEC := 0x00 [1 byte]
|
||
HTYPE := 0xFFFF [2 bytes]
|
||
HLEN := 0x0000 [2 bytes]
|
||
HDATA := SERVER_HDATA[462,954] [492 bytes]
|
||
}
|
||
EXTEND2 {
|
||
NSPEC := 0x00 [1 byte]
|
||
HTYPE := 0xFFFF [2 bytes]
|
||
HLEN := 0x0000 [2 bytes]
|
||
HDATA := SERVER_HDATA[955,1447] [492 bytes]
|
||
}
|
||
EXTEND2 {
|
||
NSPEC := 0x00 [1 byte]
|
||
HTYPE := 0xFFFF [2 bytes]
|
||
HLEN := 0x0000 [2 bytes]
|
||
HDATA := SERVER_HDATA[1448,1939] [492 bytes]
|
||
}
|
||
EXTEND2 {
|
||
NSPEC := 0x00 [1 byte]
|
||
HTYPE := 0xFFFF [2 bytes]
|
||
HLEN := 0x0000 [2 bytes]
|
||
HDATA := SERVER_HDATA[1940,2112] [172 bytes]
|
||
}
|
||
|
||
|
||
§B. NewHope Internal Functions
|
||
|
||
gen_a(SEED): returns a uniformly random poly
|
||
poly_getnoise(): returns a poly sampled from a centered binomial
|
||
poly_ntt(poly): number-theoretic transform; returns a poly
|
||
poly_invntt(poly): inverse number-theoretic transform; returns a poly
|
||
poly_pointwise(poly, poly): pointwise multiplication; returns a poly
|
||
poly_tobytes(poly): packs a poly to a NEWHOPE_POLY byte array
|
||
poly_frombytes(NEWHOPE_POLY): unpacks a NEWHOPE_POLY byte array to a poly
|
||
|
||
helprec(poly): returns a NEWHOPE_REC byte array
|
||
rec(poly, NEWHOPE_REC): returns a NEWHOPE_KEY
|
||
|
||
|
||
--- Description of the Newhope internal functions ---
|
||
|
||
gen_a(SEED seed) receives as input a 32-byte (public) seed. It expands
|
||
this seed through SHAKE-128 from the FIPS202 standard. The output of SHAKE-128
|
||
is considered a sequence of 16-bit little-endian integers. This sequence is
|
||
used to initialize the coefficients of the returned polynomial from the least
|
||
significant (coefficient of X^0) to the most significant (coefficient of
|
||
X^1023) coefficient. For each of the 16-bit integers first eliminate the
|
||
highest two bits (to make it a 14-bit integer) and then use it as the next
|
||
coefficient if it is smaller than q=12289.
|
||
Note that the amount of output required from SHAKE to initialize all 1024
|
||
coefficients of the polynomial varies depending on the input seed.
|
||
Note further that this function does not process any secret data and thus does
|
||
not need any timing-attack protection.
|
||
|
||
|
||
poly_getnoise() first generates 4096 bytes of uniformly random data. This can
|
||
be done by reading these bytes from the system's RNG; efficient
|
||
implementations will typically only read a 32-byte seed from the system's RNG
|
||
and expand it through some fast PRG (for example, ChaCha20 or AES-256 in CTR
|
||
mode). The output of the PRG is considered an array of 2048 16-bit integers
|
||
r[0],...,r[2047]. The coefficients of the output polynomial are computed as
|
||
HW(r[0])-HW(r[1]), HW(r[2])-HW(r[3]),...,HW(r[2046])-HW(r[2047]), where HW
|
||
stands for Hamming weight.
|
||
Note that the choice of RNG is a local decision; different implementations are
|
||
free to use different RNGs.
|
||
Note further that the output of this function is secret; the PRG (and the
|
||
computation of HW) need to be protected against timing attacks.
|
||
|
||
|
||
poly_ntt(poly f): For a mathematical description of poly_ntt see the [0]; a
|
||
pseudocode description of a very naive in-place transformation of an input
|
||
polynomial f = f[0] + f[1]*X + f[2]*X^2 + ... + f[1023]*X^1023 is the
|
||
following code (all arithmetic on coefficients performed modulo q):
|
||
|
||
psi = 7
|
||
omega = 49
|
||
|
||
for i in range(0,n):
|
||
t[i] = f[i] * psi^i
|
||
|
||
for i in range(0,n):
|
||
f[i] = 0
|
||
for j in range(0,n):
|
||
f[i] += t[j] * omega^((i*j)%n)
|
||
|
||
Note that this is not how poly_ntt should be implemented if performance is
|
||
an issue; in particular, efficient algorithms for the number-theoretic
|
||
transform take time O(n*log(n)) and not O(n^2)
|
||
Note further that all arithmetic in poly_ntt has to be protected against
|
||
timing attacks.
|
||
|
||
|
||
poly_invntt(poly f): For a mathematical description of poly_invntt see the
|
||
[0]; a pseudocode description of a very naive in-place transformation of an
|
||
input polynomial f = f[0] + f[1]*X + f[2]*X^2 + ... + f[1023]*X^1023 is the
|
||
following code (all arithmetic on coefficients performed modulo q):
|
||
|
||
invpsi = 8778;
|
||
invomega = 1254;
|
||
invn = 12277;
|
||
|
||
for i in range(0,n):
|
||
t[i] = f[i];
|
||
|
||
for i in range(0,n):
|
||
f[i]=0;
|
||
for j in range(0,n):
|
||
f[i] += t[j] * invomega^((i*j)%n)
|
||
f[i] *= invpsi^i
|
||
f[i] *= invn
|
||
|
||
Note that this is not how poly_invntt should be implemented if performance
|
||
is an issue; in particular, efficient algorithms for the inverse
|
||
number-theoretic transform take time O(n*log(n)) and not O(n^2)
|
||
Note further that all arithmetic in poly_invntt has to be protected against
|
||
timing attacks.
|
||
|
||
|
||
poly_pointwise(poly f, poly g) performs pointwise multiplication of the two
|
||
polynomials. This means that for f = (f0 + f1*X + f2*X^2 + ... +
|
||
f1023*X^1023) and g = (g0 + g1*X + g2*X^2 + ... + g1023*X^1023) it computes
|
||
and returns h = (h0 + h1*X + h2*X^2 + ... + h1023*X^1023) with h0 = f0*g0,
|
||
h1 = f1*g1,..., h1023 = f1023*g1023.
|
||
|
||
|
||
poly_tobytes(poly f) first reduces all coefficents of f modulo q, i.e.,
|
||
brings them to the interval [0,q-1]. Denote these reduced coefficients as
|
||
f0,..., f1023; note that they all fit into 14 bits. The function then packs
|
||
those coefficients into an array of 1792 bytes r[0],..., r[1792] in "packed
|
||
little-endian representation", i.e.,
|
||
r[0] = f[0] & 0xff;
|
||
r[1] = (f[0] >> 8) & ((f[1] & 0x03) << 6)
|
||
r[2] = (f[1] >> 2) & 0xff;
|
||
r[3] = (f[1] >> 10) & ((f[2] & 0x0f) << 4)
|
||
.
|
||
.
|
||
.
|
||
r[1790] = (f[1022]) >> 12) & ((f[1023] & 0x3f) << 2)
|
||
r[1791] = f[1023] >> 6
|
||
Note that this function needs to be protected against timing attacks. In
|
||
particular, avoid non-constant-time conditional subtractions (or other
|
||
non-constant-time expressions) in the reduction modulo q of the coefficients.
|
||
|
||
|
||
poly_frombytes(NEWHOPE_POLY b) is the inverse of poly_tobytes; it receives
|
||
as input an array of 1792 bytes and coverts it into the internal
|
||
representation of a poly. Note that poly_frombytes does not need to check
|
||
whether the coefficients are reduced modulo q or reduce coefficients modulo
|
||
q. Note further that the function must not leak any information about its
|
||
inputs through timing information, as it is also applied to the secret key
|
||
of the initiator.
|
||
|
||
|
||
helprec(poly f) computes 256 bytes of reconciliation information from the
|
||
input poly f. Internally, one byte of reconciliation information is computed
|
||
from four coefficients of f by a function helprec4. Let the input polynomial f
|
||
= (f0 + f1*X + f2*X^2 + ... + f1023*X^1023); let the output byte array be
|
||
r[0],...r[256]. This output byte array is computed as
|
||
r[0] = helprec4(f0,f256,f512,f768)
|
||
r[1] = helprec4(f1,f257,f513,f769)
|
||
r[2] = helprec4(f2,f258,f514,f770)
|
||
.
|
||
.
|
||
.
|
||
r[255] = helprec4(f255,f511,f767,f1023), where helprec4 does the following:
|
||
|
||
helprec4(x0,x1,x2,x3):
|
||
b = randombit()
|
||
r0,r1,r2,r3 = CVPD4(8*x0+4*b,8*x1+4*b,8*x2+4*b,8*x3+4*b)
|
||
r = (r0 & 0x03) | ((r1 & 0x03) << 2) | ((r2 & 0x03) << 4) | ((r3 & 0x03) << 6)
|
||
return r
|
||
|
||
The function CVPD4 does the following:
|
||
|
||
CVPD4(y0,y1,y2,y3):
|
||
v00 = round(y0/2q)
|
||
v01 = round(y1/2q)
|
||
v02 = round(y2/2q)
|
||
v03 = round(y3/2q)
|
||
v10 = round((y0-1)/2q)
|
||
v11 = round((y1-1)/2q)
|
||
v12 = round((y2-1)/2q)
|
||
v13 = round((y3-1)/2q)
|
||
t = abs(y0 - 2q*v00)
|
||
t += abs(y1 - 2q*v01)
|
||
t += abs(y2 - 2q*v02)
|
||
t += abs(y3 - 2q*v03)
|
||
if(t < 2q):
|
||
v0 = v00
|
||
v1 = v01
|
||
v2 = v02
|
||
v3 = v03
|
||
k = 0
|
||
else
|
||
v0 = v10
|
||
v1 = v11
|
||
v2 = v12
|
||
v3 = v13
|
||
r = 1
|
||
return (v0-v3,v1-v3,v2-v3,k+2*v3)
|
||
|
||
In this description, round(x) is defined as ⌊x + 0.5⌋, where ⌊x⌋ rounds to
|
||
the largest integer that does not exceed x; abs() returns the absolute
|
||
value.
|
||
Note that all computations involved in helprec operate on secret data and must
|
||
be protected against timing attacks.
|
||
|
||
|
||
rec(poly f, NEWHOPE_REC r) computes the pre-hash (see paper) Newhope key from
|
||
f and r. Specifically, it computes one bit of key from 4 coefficients of f and
|
||
one byte of r. Let f = f0 + f1*X + f2*X^2 + ... + f1023*X^1023 and let r =
|
||
r[0],r[1],...,r[255]. Let the bytes of the output by k[0],...,k[31] and let
|
||
the bits of the output by k0,...,k255, where
|
||
k0 = k[0] & 0x01
|
||
k1 = (k[0] >> 1) & 0x01
|
||
k2 = (k[0] >> 2) & 0x01
|
||
.
|
||
.
|
||
.
|
||
k8 = k[1] & 0x01
|
||
k9 = (k[1] >> 1) & 0x01
|
||
.
|
||
.
|
||
.
|
||
k255 = (k[32] >> 7)
|
||
The function rec computes k0,...,k255 as
|
||
k0 = rec4(f0,f256,f512,f768,r[0])
|
||
k1 = rec4(f1,f257,f513,f769,r[1])
|
||
.
|
||
.
|
||
.
|
||
k255 = rec4(f255,f511,f767,f1023,r[255])
|
||
|
||
The function rec4 does the following:
|
||
|
||
rec4(y0,y1,y2,y3,r):
|
||
r0 = r & 0x03
|
||
r1 = (r >> 2) & 0x03
|
||
r2 = (r >> 4) & 0x03
|
||
r3 = (r >> 6) & 0x03
|
||
Decode(8*y0-2q*r0, 8*y1-2q*r1, 8*y2-2q*r2, 8*y3-q*r3)
|
||
|
||
The function Decode does the following:
|
||
|
||
Decode(v0,v1,v2,v3):
|
||
t0 = round(v0/8q)
|
||
t1 = round(v1/8q)
|
||
t2 = round(v2/8q)
|
||
t3 = round(v3/8q)
|
||
t = abs(v0 - 8q*t0)
|
||
t += abs(v0 - 8q*t0)
|
||
t += abs(v0 - 8q*t0)
|
||
t += abs(v0 - 8q*t0)
|
||
if(t > 1) return 1
|
||
else return 0
|
||
|
||
|
||
§C. Test Vectors
|