/* * Implementation of OpenSSH 9.x's hybrid key exchange protocol * sntrup761x25519-sha512@openssh.com . * * This consists of the 'Streamlined NTRU Prime' quantum-resistant * cryptosystem, run in parallel with ordinary Curve25519 to generate * a shared secret combining the output of both systems. * * (Hence, even if you don't trust this newfangled NTRU Prime thing at * all, it's at least no _less_ secure than the kex you were using * already.) * * References for the NTRU Prime cryptosystem, up to and including * binary encodings of public and private keys and the exact preimages * of the hashes used in key exchange: * * https://ntruprime.cr.yp.to/ * https://ntruprime.cr.yp.to/nist/ntruprime-20201007.pdf * * The SSH protocol layer is not documented anywhere I could find (as * of 2022-04-15, not even in OpenSSH's PROTOCOL.* files). I had to * read OpenSSH's source code to find out how it worked, and the * answer is as follows: * * This hybrid kex method is treated for SSH purposes as a form of * elliptic-curve Diffie-Hellman, and shares the same SSH message * sequence: client sends SSH2_MSG_KEX_ECDH_INIT containing its public * half, server responds with SSH2_MSG_KEX_ECDH_REPLY containing _its_ * public half plus the host key and signature on the shared secret. * * (This is a bit of a fudge, because unlike actual ECDH, this kex * method is asymmetric: one side sends a public key, and the other * side encrypts something with it and sends the ciphertext back. So * while the normal ECDH implementations can compute the two sides * independently in parallel, this system reusing the same messages * has to be serial. But the order of the messages _is_ firmly * specified in SSH ECDH, so it works anyway.) * * For this kex method, SSH2_MSG_KEX_ECDH_INIT still contains a single * SSH 'string', which consists of the concatenation of a Streamlined * NTRU Prime public key with the Curve25519 public value. (Both of * these have fixed length in bytes, so there's no ambiguity in the * concatenation.) * * SSH2_MSG_KEX_ECDH_REPLY is mostly the same as usual. The only * string in the packet that varies is the second one, which would * normally contain the server's public elliptic curve point. Instead, * it now contains the concatenation of * * - a Streamlined NTRU Prime ciphertext * - the 'confirmation hash' specified in ntruprime-20201007.pdf, * hashing the plaintext of that ciphertext together with the * public key * - the Curve25519 public point as usual. * * Again, all three of those elements have fixed lengths. * * The client decrypts the ciphertext, checks the confirmation hash, * and if successful, generates the 'session hash' specified in * ntruprime-20201007.pdf, which is 32 bytes long and is the ultimate * output of the Streamlined NTRU Prime key exchange. * * The output of the hybrid kex method as a whole is an SSH 'string' * of length 64 containing the SHA-512 hash of the concatenatio of * * - the Streamlined NTRU Prime session hash (32 bytes) * - the Curve25519 shared secret (32 bytes). * * That string is included directly into the SSH exchange hash and key * derivation hashes, in place of the mpint that comes out of most * other kex methods. */ #include #include #include #include "putty.h" #include "ssh.h" #include "mpint.h" #include "ntru.h" #include "smallmoduli.h" /* Invert x mod q, assuming it's nonzero. (For time-safety, no check * is made for zero; it just returns 0.) * * Expects qrecip == reciprocal_for_reduction(q). (But it's passed in * as a parameter to save recomputing it, on the theory that the * caller will have had it lying around already in most cases.) */ static uint16_t invert(uint16_t x, uint16_t q, uint64_t qrecip) { /* Fermat inversion: compute x^(q-2), since x^(q-1) == 1. */ uint32_t sq = x, bit = 1, acc = 1, exp = q-2; while (1) { if (exp & bit) { acc = reduce(acc * sq, q, qrecip); exp &= ~bit; if (!exp) return acc; } sq = reduce(sq * sq, q, qrecip); bit <<= 1; } } /* Check whether x == 0, time-safely, and return 1 if it is or 0 otherwise. */ static unsigned iszero(uint16_t x) { return 1 & ~((x + 0xFFFF) >> 16); } /* * Handy macros to cut down on all those extra function parameters. In * the common case where a function is working mod the same modulus * throughout (and has called it q), you can just write 'SETUP;' at * the top and then call REDUCE(...) and INVERT(...) without having to * write out q and qrecip every time. */ #define SETUP uint64_t qrecip = reciprocal_for_reduction(q) #define REDUCE(x) reduce(x, q, qrecip) #define INVERT(x) invert(x, q, qrecip) /* ---------------------------------------------------------------------- * Quotient-ring functions. * * NTRU Prime works with two similar but different quotient rings: * * Z_q[x] / where p,q are the prime parameters of the system * Z_3[x] / with the same p, but coefficients mod 3. * * The former is a field (every nonzero element is invertible), * because the system parameters are chosen such that x^p-x-1 is * invertible over Z_q. The latter is not a field (or not necessarily, * and in particular, not for the value of p we use here). * * In these core functions, you pass in the modulus you want as the * parameter q, which is either the 'real' q specified in the system * parameters, or 3 if you're doing one of the mod-3 parts of the * algorithm. */ /* * Multiply two elements of a quotient ring. * * 'a' and 'b' are arrays of exactly p coefficients, with constant * term first. 'out' is an array the same size to write the inverse * into. */ void ntru_ring_multiply(uint16_t *out, const uint16_t *a, const uint16_t *b, unsigned p, unsigned q) { SETUP; /* * Strategy: just compute the full product with 2p coefficients, * and then reduce it mod x^p-x-1 by working downwards from the * top coefficient replacing x^{p+k} with (x+1)x^k for k = ...,1,0. * * Possibly some speed could be gained here by doing the recursive * Karatsuba optimisation for the initial multiplication? But I * haven't tried it. */ uint32_t *unreduced = snewn(2*p, uint32_t); for (unsigned i = 0; i < 2*p; i++) unreduced[i] = 0; for (unsigned i = 0; i < p; i++) for (unsigned j = 0; j < p; j++) unreduced[i+j] = REDUCE(unreduced[i+j] + a[i] * b[j]); for (unsigned i = 2*p - 1; i >= p; i--) { unreduced[i-p] += unreduced[i]; unreduced[i-p+1] += unreduced[i]; unreduced[i] = 0; } for (unsigned i = 0; i < p; i++) out[i] = REDUCE(unreduced[i]); smemclr(unreduced, 2*p * sizeof(*unreduced)); sfree(unreduced); } /* * Invert an element of the quotient ring. * * 'in' is an array of exactly p coefficients, with constant term * first. 'out' is an array the same size to write the inverse into. * * Method: essentially Stein's gcd algorithm, taking the gcd of the * input (regarded as an element of Z_q[x] proper) and x^p-x-1. Given * two polynomials over a field which are not both divisible by x, you * can find their gcd by iterating the following procedure: * * - if one is divisible by x, divide off x * - otherwise, subtract from the higher-degree one whatever scalar * multiple of the lower-degree one will make it divisible by x, * and _then_ divide off x * * Neither of these types of step changes the gcd of the two * polynomials. * * Each step reduces the sum of the two polynomials' degree by at * least one, as long as at least one of the degrees is positive. * (Maybe more than one if all the stars align in the second case, if * the subtraction cancels the leading term as well as the constant * term.) So in at most deg A + deg B steps, we must have reached the * situation where both polys are constants; in one more step after * that, one of them will be zero; and in one step after _that_, the * zero one will reliably be the one we're dividing by x. Or rather, * that's what happens in the case where A,B are coprime; if not, then * one hits zero while the other is still nonzero. * * In a normal gcd algorithm, you'd track a linear combination of the * two original polynomials that yields each working value, and end up * with a linear combination of the inputs that yields the gcd. In * this algorithm, the 'divide off x' step makes that awkward - but we * can solve that by instead multiplying by the inverse of x in the * ring that we want our answer to be valid in! And since the modulus * polynomial of the ring is x^p-x-1, the inverse of x is easy to * calculate, because it's always just x^{p-1} - 1, which is also very * easy to multiply by. */ unsigned ntru_ring_invert(uint16_t *out, const uint16_t *in, unsigned p, unsigned q) { SETUP; /* Size of the polynomial arrays we'll work with */ const size_t SIZE = p+1; /* Number of steps of the algorithm is the max possible value of * deg A + deg B + 2, where deg A <= p-1 and deg B = p */ const size_t STEPS = 2*p + 1; /* Our two working polynomials */ uint16_t *A = snewn(SIZE, uint16_t); uint16_t *B = snewn(SIZE, uint16_t); /* Coefficient of the input value in each one */ uint16_t *Ac = snewn(SIZE, uint16_t); uint16_t *Bc = snewn(SIZE, uint16_t); /* Initialise A to the input, and Ac correspondingly to 1 */ memcpy(A, in, p*sizeof(uint16_t)); A[p] = 0; Ac[0] = 1; for (size_t i = 1; i < SIZE; i++) Ac[i] = 0; /* Initialise B to the quotient polynomial of the ring, x^p-x-1 * And Bc = 0 */ B[0] = B[1] = q-1; for (size_t i = 2; i < p; i++) B[i] = 0; B[p] = 1; for (size_t i = 0; i < SIZE; i++) Bc[i] = 0; /* Run the gcd-finding algorithm. */ for (size_t i = 0; i < STEPS; i++) { /* * First swap round so that A is the one we'll be dividing by x. * * In the case where one of the two polys has a zero constant * term, it's that one. In the other case, it's the one of * smaller degree. We must compute both, and choose between * them in a side-channel-safe way. */ unsigned x_divides_A = iszero(A[0]); unsigned x_divides_B = iszero(B[0]); unsigned B_is_bigger = 0; { unsigned not_seen_top_term_of_A = 1, not_seen_top_term_of_B = 1; for (size_t j = SIZE; j-- > 0 ;) { not_seen_top_term_of_A &= iszero(A[j]); not_seen_top_term_of_B &= iszero(B[j]); B_is_bigger |= (~not_seen_top_term_of_B & not_seen_top_term_of_A); } } unsigned need_swap = x_divides_B | (~x_divides_A & B_is_bigger); uint16_t swap_mask = -need_swap; for (size_t j = 0; j < SIZE; j++) { uint16_t diff = (A[j] ^ B[j]) & swap_mask; A[j] ^= diff; B[j] ^= diff; } for (size_t j = 0; j < SIZE; j++) { uint16_t diff = (Ac[j] ^ Bc[j]) & swap_mask; Ac[j] ^= diff; Bc[j] ^= diff; } /* * Replace A with a linear combination of both A and B that * has constant term zero, which we do by calculating * * (constant term of B) * A - (constant term of A) * B * * In one of the two cases, A's constant term is already zero, * so the coefficient of B will be zero too; hence, this will * do nothing useful (it will merely scale A by some scalar * value), but it will take the same length of time as doing * something, which is just what we want. */ uint16_t Amult = B[0], Bmult = q - A[0]; for (size_t j = 0; j < SIZE; j++) A[j] = REDUCE(Amult * A[j] + Bmult * B[j]); /* And do the same transformation to Ac */ for (size_t j = 0; j < SIZE; j++) Ac[j] = REDUCE(Amult * Ac[j] + Bmult * Bc[j]); /* * Now divide A by x, and compensate by multiplying Ac by * x^{p-1}-1 mod x^p-x-1. * * That multiplication is particularly easy, precisely because * x^{p-1}-1 is the multiplicative inverse of x! Each x^n term * for n>0 just moves down to the x^{n-1} term, and only the * constant term has to be dealt with in an interesting way. */ for (size_t j = 1; j < SIZE; j++) A[j-1] = A[j]; A[SIZE-1] = 0; uint16_t Ac0 = Ac[0]; for (size_t j = 1; j < p; j++) Ac[j-1] = Ac[j]; Ac[p-1] = Ac0; Ac[0] = REDUCE(Ac[0] + q - Ac0); } /* * Now we expect that A is 0, and B is a constant. If so, then * they are coprime, and we're going to return success. If not, * they have a common factor. */ unsigned success = iszero(A[0]) & (1 ^ iszero(B[0])); for (size_t j = 1; j < SIZE; j++) success &= iszero(A[j]) & iszero(B[j]); /* * So we're going to return Bc, but first, scale it by the * multiplicative inverse of the constant we ended up with in * B[0]. */ uint16_t scale = INVERT(B[0]); for (size_t i = 0; i < p; i++) out[i] = REDUCE(scale * Bc[i]); smemclr(A, SIZE * sizeof(*A)); sfree(A); smemclr(B, SIZE * sizeof(*B)); sfree(B); smemclr(Ac, SIZE * sizeof(*Ac)); sfree(Ac); smemclr(Bc, SIZE * sizeof(*Bc)); sfree(Bc); return success; } /* * Given an array of values mod q, convert each one to its * minimum-absolute-value representative, and then reduce mod 3. * * Output values are 0, 1 and 0xFFFF, representing -1. * * (Normally our arrays of uint16_t are in 'minimal non-negative * residue' form, so the output of this function is unusual. But it's * useful to have it in this form so that it can be reused by * ntru_round3. You can put it back to the usual representation using * ntru_normalise, below.) */ void ntru_mod3(uint16_t *out, const uint16_t *in, unsigned p, unsigned q) { uint64_t qrecip = reciprocal_for_reduction(q); uint64_t recip3 = reciprocal_for_reduction(3); unsigned bias = q/2; uint16_t adjust = 3 - reduce(bias-1, 3, recip3); for (unsigned i = 0; i < p; i++) { uint16_t val = reduce(in[i] + bias, q, qrecip); uint16_t residue = reduce(val + adjust, 3, recip3); out[i] = residue - 1; } } /* * Given an array of values mod q, round each one to the nearest * multiple of 3 to its minimum-absolute-value representative. * * Output values are signed integers coerced to uint16_t, so again, * use ntru_normalise afterwards to put them back to normal. */ void ntru_round3(uint16_t *out, const uint16_t *in, unsigned p, unsigned q) { SETUP; unsigned bias = q/2; ntru_mod3(out, in, p, q); for (unsigned i = 0; i < p; i++) out[i] = REDUCE(in[i] + bias) - bias - out[i]; } /* * Given an array of signed integers coerced to uint16_t in the range * [-q/2,+q/2], normalise them back to mod q values. */ static void ntru_normalise(uint16_t *out, const uint16_t *in, unsigned p, unsigned q) { for (unsigned i = 0; i < p; i++) out[i] = in[i] + q * (in[i] >> 15); } /* * Given an array of values mod q, add a constant to each one. */ void ntru_bias(uint16_t *out, const uint16_t *in, unsigned bias, unsigned p, unsigned q) { SETUP; for (unsigned i = 0; i < p; i++) out[i] = REDUCE(in[i] + bias); } /* * Given an array of values mod q, multiply each one by a constant. */ void ntru_scale(uint16_t *out, const uint16_t *in, uint16_t scale, unsigned p, unsigned q) { SETUP; for (unsigned i = 0; i < p; i++) out[i] = REDUCE(in[i] * scale); } /* * Given an array of values mod 3, convert them to values mod q in a * way that maps -1,0,+1 to -1,0,+1. */ static void ntru_expand( uint16_t *out, const uint16_t *in, unsigned p, unsigned q) { for (size_t i = 0; i < p; i++) { uint16_t v = in[i]; /* Map 2 to q-1, and leave 0 and 1 unchanged */ v += (v >> 1) * (q-3); out[i] = v; } } /* ---------------------------------------------------------------------- * Implement the binary encoding from ntruprime-20201007.pdf, which is * used to encode public keys and ciphertexts (though not plaintexts, * which are done in a much simpler way). * * The general idea is that your encoder takes as input a list of * small non-negative integers (r_i), and a sequence of limits (m_i) * such that 0 <= r_i < m_i, and emits a sequence of bytes that encode * all of these as tightly as reasonably possible. * * That's more general than is really needed, because in both the * actual uses of this encoding, the input m_i are all the same! But * the array of (r_i,m_i) pairs evolves during encoding, so they don't * _stay_ all the same, so you still have to have all the generality. * * The encoding process makes a number of passes along the list of * inputs. In each step, pairs of adjacent numbers are combined into * one larger one by turning (r_i,m_i) and (r_{i+1},m_{i+1}) into the * pair (r_i + m_i r_{i+1}, m_i m_{i+1}), i.e. so that the original * numbers could be recovered by taking the quotient and remaiinder of * the new r value by m_i. Then, if the new m_i is at least 2^14, we * emit the low 8 bits of r_i to the output stream and reduce r_i and * its limit correspondingly. So at the end of the pass, we've got * half as many numbers still to encode, they're all still not too * big, and we've emitted some amount of data into the output. Then do * another pass, keep going until there's only one number left, and * emit it little-endian. * * That's all very well, but how do you decode it again? DJB exhibits * a pair of recursive functions that are supposed to be mutually * inverse, but I didn't have any confidence that I'd be able to debug * them sensibly if they turned out not to be (or rather, if I * implemented one of them wrong). So I came up with my own strategy * instead. * * In my strategy, we start by processing just the (m_i) into an * 'encoding schedule' consisting of a sequence of simple * instructions. The instructions operate on a FIFO queue of numbers, * initialised to the original (r_i). The three instruction types are: * * - 'COMBINE': consume two numbers a,b from the head of the queue, * combine them by calculating a + m*b for some specified m, and * push the result on the tail of the queue. * * - 'BYTE': divide the tail element of the queue by 2^8 and emit the * low bits into the output stream. * * - 'COPY': pop a number from the head of the queue and push it * straight back on the tail. (Used for handling the leftover * element at the end of a pass if the input to the pass was a list * of odd length.) * * So we effectively implement DJB's encoding process in simulation, * and instead of actually processing a set of (r_i), we 'compile' the * process into a sequence of instructions that can be handed just the * (r_i) later and encode them in the right way. At the end of the * instructions, the queue is expected to have been reduced to length * 1 and contain the single integer 0. * * The nice thing about this system is that each of those three * instructions is easy to reverse. So you can also use the same * instructions for decoding: start with a queue containing 0, and * process the instructions in reverse order and reverse sense. So * BYTE means to _consume_ a byte from the encoded data (starting from * the rightmost end) and use it to make a queue element bigger; and * COMBINE run in reverse pops a single element from one end of the * queue, divides it by m, and pushes the quotient and remainder on * the other end. * * (So it's easy to debug, because the queue passes through the exact * same sequence of states during decoding that it did during * encoding, just in reverse order.) * * Also, the encoding schedule comes with information about the * expected size of the encoded data, because you can find that out * easily by just counting the BYTE commands. */ enum { /* * Command values appearing in the 'ops' array. ENC_COPY and * ENC_BYTE are single values; values of the form * (ENC_COMBINE_BASE + m) represent a COMBINE command with * parameter m. */ ENC_COPY, ENC_BYTE, ENC_COMBINE_BASE }; struct NTRUEncodeSchedule { /* * Object representing a compiled set of encoding instructions. * * 'nvals' is the number of r_i we expect to encode. 'nops' is the * number of encoding commands in the 'ops' list; 'opsize' is the * physical size of the array, used during construction. * * 'endpos' is used to avoid a last-minute faff during decoding. * We implement our FIFO of integers as a ring buffer of size * 'nvals'. Encoding cycles round it some number of times, and the * final 0 element ends up at some random location in the array. * If we know _where_ the 0 ends up during encoding, we can put * the initial 0 there at the start of decoding, and then when we * finish reversing all the instructions, we'll end up with the * output numbers already arranged at their correct positions, so * that there's no need to rotate the array at the last minute. */ size_t nvals, endpos, nops, opsize; uint32_t *ops; }; static inline void sched_append(NTRUEncodeSchedule *sched, uint16_t op) { /* Helper function to append an operation to the schedule, and * update endpos. */ sgrowarray(sched->ops, sched->opsize, sched->nops); sched->ops[sched->nops++] = op; if (op != ENC_BYTE) sched->endpos = (sched->endpos + 1) % sched->nvals; } /* * Take in the list of limit values (m_i) and compute the encoding * schedule. */ NTRUEncodeSchedule *ntru_encode_schedule(const uint16_t *ms_in, size_t n) { NTRUEncodeSchedule *sched = snew(NTRUEncodeSchedule); sched->nvals = n; sched->endpos = n-1; sched->nops = sched->opsize = 0; sched->ops = NULL; assert(n != 0); /* * 'ms' is the list of (m_i) on input to the current pass. * 'ms_new' is the list output from the current pass. After each * pass we swap the arrays round. */ uint32_t *ms = snewn(n, uint32_t); uint32_t *msnew = snewn(n, uint32_t); for (size_t i = 0; i < n; i++) ms[i] = ms_in[i]; while (n > 1) { size_t nnew = 0; for (size_t i = 0; i < n; i += 2) { if (i+1 == n) { /* * Odd element at the end of the input list: just copy * it unchanged to the output. */ sched_append(sched, ENC_COPY); msnew[nnew++] = ms[i]; break; } /* * Normal case: consume two elements from the input list * and combine them. */ uint32_t m1 = ms[i], m2 = ms[i+1], m = m1*m2; sched_append(sched, ENC_COMBINE_BASE + m1); /* * And then, as long as the combined limit is big enough, * emit an output byte from the bottom of it. */ while (m >= (1<<14)) { sched_append(sched, ENC_BYTE); m = (m + 0xFF) >> 8; } /* * Whatever is left after that, we emit into the output * list and append to the fifo. */ msnew[nnew++] = m; } /* * End of pass. The output list of (m_i) now becomes the input * list. */ uint32_t *tmp = ms; ms = msnew; n = nnew; msnew = tmp; } /* * When that loop terminates, it's because there's exactly one * number left to encode. (Or, technically, _at most_ one - but we * don't support encoding a completely empty list in this * implementation, because what would be the point?) That number * is just emitted little-endian until its limit is 1 (meaning its * only possible actual value is 0). */ assert(n == 1); uint32_t m = ms[0]; while (m > 1) { sched_append(sched, ENC_BYTE); m = (m + 0xFF) >> 8; } sfree(ms); sfree(msnew); return sched; } void ntru_encode_schedule_free(NTRUEncodeSchedule *sched) { sfree(sched->ops); sfree(sched); } /* * Calculate the output length of the encoded data in bytes. */ size_t ntru_encode_schedule_length(NTRUEncodeSchedule *sched) { size_t len = 0; for (size_t i = 0; i < sched->nops; i++) if (sched->ops[i] == ENC_BYTE) len++; return len; } /* * Retrieve the number of items encoded. (Used by testcrypt.) */ size_t ntru_encode_schedule_nvals(NTRUEncodeSchedule *sched) { return sched->nvals; } /* * Actually encode a sequence of (r_i), emitting the output bytes to * an arbitrary BinarySink. */ void ntru_encode(NTRUEncodeSchedule *sched, const uint16_t *rs_in, BinarySink *bs) { size_t n = sched->nvals; uint32_t *rs = snewn(n, uint32_t); for (size_t i = 0; i < n; i++) rs[i] = rs_in[i]; /* * The head and tail pointers of the queue are both 'full'. That * is, rs[head] is the first element actually in the queue, and * rs[tail] is the last element. * * So you append to the queue by first advancing 'tail' and then * writing to rs[tail], whereas you consume from the queue by * first reading rs[head] and _then_ advancing 'head'. * * The more normal thing would be to make 'tail' point to the * first empty slot instead of the last full one. But then you'd * have to faff about with modular arithmetic to find the last * full slot for the BYTE command, so in this case, it's easier to * do it the less usual way. */ size_t head = 0, tail = n-1; for (size_t i = 0; i < sched->nops; i++) { uint16_t op = sched->ops[i]; switch (op) { case ENC_BYTE: put_byte(bs, rs[tail] & 0xFF); rs[tail] >>= 8; break; case ENC_COPY: { uint32_t r = rs[head]; head = (head + 1) % n; tail = (tail + 1) % n; rs[tail] = r; break; } default: { uint32_t r1 = rs[head]; head = (head + 1) % n; uint32_t r2 = rs[head]; head = (head + 1) % n; tail = (tail + 1) % n; rs[tail] = r1 + (op - ENC_COMBINE_BASE) * r2; break; } } } /* * Expect that we've ended up with a single zero in the queue, at * exactly the position that the setup-time analysis predicted it. */ assert(head == sched->endpos); assert(tail == sched->endpos); assert(rs[head] == 0); smemclr(rs, n * sizeof(*rs)); sfree(rs); } /* * Decode a ptrlen of binary data into a sequence of (r_i). The data * is expected to be of exactly the right length (on pain of assertion * failure). */ void ntru_decode(NTRUEncodeSchedule *sched, uint16_t *rs_out, ptrlen data) { size_t n = sched->nvals; const uint8_t *base = (const uint8_t *)data.ptr; const uint8_t *pos = base + data.len; /* * Initialise the queue to a single zero, at the 'endpos' position * that will mean the final output is correctly aligned. * * 'head' and 'tail' have the same meanings as in encoding. So * 'tail' is the location that BYTE modifies and COPY and COMBINE * consume from, and 'head' is the location that COPY and COMBINE * push on to. As in encoding, they both point at the extremal * full slots in the array. */ uint32_t *rs = snewn(n, uint32_t); size_t head = sched->endpos, tail = head; rs[tail] = 0; for (size_t i = sched->nops; i-- > 0 ;) { uint16_t op = sched->ops[i]; switch (op) { case ENC_BYTE: { assert(pos > base); uint8_t byte = *--pos; rs[tail] = (rs[tail] << 8) | byte; break; } case ENC_COPY: { uint32_t r = rs[tail]; tail = (tail + n - 1) % n; head = (head + n - 1) % n; rs[head] = r; break; } default: { uint32_t r = rs[tail]; tail = (tail + n - 1) % n; uint32_t m = op - ENC_COMBINE_BASE; uint64_t mrecip = reciprocal_for_reduction(m); uint32_t r1, r2; r1 = reduce_with_quot(r, &r2, m, mrecip); head = (head + n - 1) % n; rs[head] = r2; head = (head + n - 1) % n; rs[head] = r1; break; } } } assert(pos == base); assert(head == 0); assert(tail == n-1); for (size_t i = 0; i < n; i++) rs_out[i] = rs[i]; smemclr(rs, n * sizeof(*rs)); sfree(rs); } /* ---------------------------------------------------------------------- * The actual public-key cryptosystem. */ struct NTRUKeyPair { unsigned p, q, w; uint16_t *h; /* public key */ uint16_t *f3, *ginv; /* private key */ uint16_t *rho; /* for implicit rejection */ }; /* Helper function to free an array of uint16_t containing a ring * element, clearing it on the way since some of them are sensitive. */ static void ring_free(uint16_t *val, unsigned p) { smemclr(val, p*sizeof(*val)); sfree(val); } void ntru_keypair_free(NTRUKeyPair *keypair) { ring_free(keypair->h, keypair->p); ring_free(keypair->f3, keypair->p); ring_free(keypair->ginv, keypair->p); ring_free(keypair->rho, keypair->p); sfree(keypair); } /* Trivial accessors used by test programs. */ unsigned ntru_keypair_p(NTRUKeyPair *keypair) { return keypair->p; } const uint16_t *ntru_pubkey(NTRUKeyPair *keypair) { return keypair->h; } /* * Generate a value of the class DJB describes as 'Short': it consists * of p terms that are all either 0 or +1 or -1, and exactly w of them * are not zero. * * Values of this kind are used for several purposes: part of the * private key, a plaintext, and the 'rho' fake-plaintext value used * for deliberately returning a duff but non-revealing session hash if * things go wrong. * * -1 is represented as 2 in the output array. So if you want these * numbers mod 3, then they come out already in the right form. * Otherwise, use ntru_expand. */ void ntru_gen_short(uint16_t *v, unsigned p, unsigned w) { /* * Get enough random data to generate a polynomial all of whose p * terms are in {0,+1,-1}, and exactly w of them are nonzero. * We'll do this by making up a completely random sequence of * {+1,-1} and then setting a random subset of them to 0. * * So we'll need p random bits to choose the nonzero values, and * then (doing it the simplest way) log2(p!) bits to shuffle them, * plus say 128 bits to ensure any fluctuations in uniformity are * negligible. * * log2(p!) is a pain to calculate, so we'll bound it above by * p*log2(p), which we bound in turn by p*16. */ size_t randbitpos = 17 * p + 128; mp_int *randdata = mp_resize(mp_random_bits(randbitpos), randbitpos + 32); /* * Initial value before zeroing out some terms: p randomly chosen * values in {1,2}. */ for (size_t i = 0; i < p; i++) v[i] = 1 + mp_get_bit(randdata, --randbitpos); /* * Hereafter we're going to extract random bits by multiplication, * treating randdata as a large fixed-point number. */ mp_reduce_mod_2to(randdata, randbitpos); /* * Zero out some terms, leaving a randomly selected w of them * nonzero. */ uint32_t nonzeros_left = w; mp_int *x = mp_new(64); for (size_t i = p; i-- > 0 ;) { /* * Pick a random number out of the number of terms remaning. */ mp_mul_integer_into(randdata, randdata, i+1); mp_rshift_fixed_into(x, randdata, randbitpos); mp_reduce_mod_2to(randdata, randbitpos); size_t j = mp_get_integer(x); /* * If that's less than nonzeros_left, then we're leaving this * number nonzero. Otherwise we're zeroing it out. */ uint32_t keep = (uint32_t)(j - nonzeros_left) >> 31; v[i] &= -keep; /* clear this field if keep == 0 */ nonzeros_left -= keep; /* decrement counter if keep == 1 */ } mp_free(x); mp_free(randdata); } /* * Make a single attempt at generating a key pair. This involves * inventing random elements of both our quotient rings and hoping * they're both invertible. * * They may not be, if you're unlucky. The element of Z_q/ * will _almost_ certainly be invertible, because that is a field, so * invertibility can only fail if you were so unlucky as to choose the * all-0s element. But the element of Z_3/ may fail to be * invertible because it has a common factor with x^p-x-1 (which, over * Z_3, is not irreducible). * * So we can't guarantee to generate a key pair in constant time, * because there's no predicting how many retries we'll need. However, * this isn't a failure of side-channel safety, because we completely * discard all the random numbers and state from each failed attempt. * So if there were a side-channel leakage from a failure, the only * thing it would give away would be a bunch of random numbers that * turned out not to be used anyway. * * But a _successful_ call to this function should execute in a * secret-independent manner, and this 'make a single attempt' * function is exposed in the API so that 'testsc' can check that. */ NTRUKeyPair *ntru_keygen_attempt(unsigned p, unsigned q, unsigned w) { /* * First invent g, which is the one more likely to fail to invert. * This is simply a uniformly random polynomial with p terms over * Z_3. So we need p*log2(3) random bits for it, plus 128 for * uniformity. It's easiest to bound log2(3) above by 2. */ size_t randbitpos = 2 * p + 128; mp_int *randdata = mp_resize(mp_random_bits(randbitpos), randbitpos + 32); /* * Select p random values from {0,1,2}. */ uint16_t *g = snewn(p, uint16_t); mp_int *x = mp_new(64); for (size_t i = 0; i < p; i++) { mp_mul_integer_into(randdata, randdata, 3); mp_rshift_fixed_into(x, randdata, randbitpos); mp_reduce_mod_2to(randdata, randbitpos); g[i] = mp_get_integer(x); } mp_free(x); mp_free(randdata); /* * Try to invert g over Z_3, and fail if it isn't invertible. */ uint16_t *ginv = snewn(p, uint16_t); if (!ntru_ring_invert(ginv, g, p, 3)) { ring_free(g, p); ring_free(ginv, p); return NULL; } /* * Fine; we have g. Now make up an f, and convert it to a * polynomial over q. */ uint16_t *f = snewn(p, uint16_t); ntru_gen_short(f, p, w); ntru_expand(f, f, p, q); /* * Multiply f by 3. */ uint16_t *f3 = snewn(p, uint16_t); ntru_scale(f3, f, 3, p, q); /* * Invert 3*f over Z_q. This is guaranteed to succeed, since * Z_q/ is a field, so the only non-invertible value is * 0. And f is nonzero because it came from ntru_gen_short (hence, * w of its components are nonzero), hence so is 3*f. */ uint16_t *f3inv = snewn(p, uint16_t); bool expect_always_success = ntru_ring_invert(f3inv, f3, p, q); assert(expect_always_success); /* * Make the public key, by converting g to a polynomial over q and * then multiplying by f3inv. */ uint16_t *g_q = snewn(p, uint16_t); ntru_expand(g_q, g, p, q); uint16_t *h = snewn(p, uint16_t); ntru_ring_multiply(h, g_q, f3inv, p, q); /* * Make up rho, used to substitute for the plaintext in the * session hash in case of confirmation failure. */ uint16_t *rho = snewn(p, uint16_t); ntru_gen_short(rho, p, w); /* * And we're done! Free everything except the pieces we're * returning. */ NTRUKeyPair *keypair = snew(NTRUKeyPair); keypair->p = p; keypair->q = q; keypair->w = w; keypair->h = h; keypair->f3 = f3; keypair->ginv = ginv; keypair->rho = rho; ring_free(f, p); ring_free(f3inv, p); ring_free(g, p); ring_free(g_q, p); return keypair; } /* * The top-level key generation function for real use (as opposed to * testsc): keep trying to make a key until you succeed. */ NTRUKeyPair *ntru_keygen(unsigned p, unsigned q, unsigned w) { while (1) { NTRUKeyPair *keypair = ntru_keygen_attempt(p, q, w); if (keypair) return keypair; } } /* * Public-key encryption. */ void ntru_encrypt(uint16_t *ciphertext, const uint16_t *plaintext, uint16_t *pubkey, unsigned p, unsigned q) { uint16_t *r_q = snewn(p, uint16_t); ntru_expand(r_q, plaintext, p, q); uint16_t *unrounded = snewn(p, uint16_t); ntru_ring_multiply(unrounded, r_q, pubkey, p, q); ntru_round3(ciphertext, unrounded, p, q); ntru_normalise(ciphertext, ciphertext, p, q); ring_free(r_q, p); ring_free(unrounded, p); } /* * Public-key decryption. */ void ntru_decrypt(uint16_t *plaintext, const uint16_t *ciphertext, NTRUKeyPair *keypair) { unsigned p = keypair->p, q = keypair->q, w = keypair->w; uint16_t *tmp = snewn(p, uint16_t); ntru_ring_multiply(tmp, ciphertext, keypair->f3, p, q); ntru_mod3(tmp, tmp, p, q); ntru_normalise(tmp, tmp, p, 3); ntru_ring_multiply(plaintext, tmp, keypair->ginv, p, 3); ring_free(tmp, p); /* * With luck, this should have recovered exactly the original * plaintext. But, as per the spec, we check whether it has * exactly w nonzero coefficients, and if not, then something has * gone wrong - and in that situation we time-safely substitute a * different output. * * (I don't know exactly why we do this, but I assume it's because * otherwise the mis-decoded output could be made to disgorge a * secret about the private key in some way.) */ unsigned weight = p; for (size_t i = 0; i < p; i++) weight -= iszero(plaintext[i]); unsigned ok = iszero(weight ^ w); /* * The default failure return value consists of w 1s followed by * 0s. */ unsigned mask = ok - 1; for (size_t i = 0; i < w; i++) { uint16_t diff = (1 ^ plaintext[i]) & mask; plaintext[i] ^= diff; } for (size_t i = w; i < p; i++) { uint16_t diff = (0 ^ plaintext[i]) & mask; plaintext[i] ^= diff; } } /* ---------------------------------------------------------------------- * Encode and decode public keys, ciphertexts and plaintexts. * * Public keys and ciphertexts use the complicated binary encoding * system implemented above. In both cases, the inputs are regarded as * symmetric about zero, and are first biased to map their most * negative permitted value to 0, so that they become non-negative and * hence suitable as inputs to the encoding system. In the case of a * ciphertext, where the input coefficients have also been coerced to * be multiples of 3, we divide by 3 as well, saving space by reducing * the upper bounds (m_i) on all the encoded numbers. */ /* * Compute the encoding schedule for a public key. */ static NTRUEncodeSchedule *ntru_encode_pubkey_schedule(unsigned p, unsigned q) { uint16_t *ms = snewn(p, uint16_t); for (size_t i = 0; i < p; i++) ms[i] = q; NTRUEncodeSchedule *sched = ntru_encode_schedule(ms, p); sfree(ms); return sched; } /* * Encode a public key. */ void ntru_encode_pubkey(const uint16_t *pubkey, unsigned p, unsigned q, BinarySink *bs) { /* Compute the biased version for encoding */ uint16_t *biased_pubkey = snewn(p, uint16_t); ntru_bias(biased_pubkey, pubkey, q / 2, p, q); /* Encode it */ NTRUEncodeSchedule *sched = ntru_encode_pubkey_schedule(p, q); ntru_encode(sched, biased_pubkey, bs); ntru_encode_schedule_free(sched); ring_free(biased_pubkey, p); } /* * Decode a public key and write it into 'pubkey'. We also return a * ptrlen pointing at the chunk of data we removed from the * BinarySource. */ ptrlen ntru_decode_pubkey(uint16_t *pubkey, unsigned p, unsigned q, BinarySource *src) { NTRUEncodeSchedule *sched = ntru_encode_pubkey_schedule(p, q); /* Retrieve the right number of bytes from the source */ size_t len = ntru_encode_schedule_length(sched); ptrlen encoded = get_data(src, len); if (get_err(src)) { /* If there wasn't enough data, give up and return all-zeroes * purely for determinism. But that value should never be * used, because the caller will also check get_err(src). */ memset(pubkey, 0, p*sizeof(*pubkey)); } else { /* Do the decoding */ ntru_decode(sched, pubkey, encoded); /* Unbias the coefficients */ ntru_bias(pubkey, pubkey, q-q/2, p, q); } ntru_encode_schedule_free(sched); return encoded; } /* * For ciphertext biasing: work out the largest absolute value a * ciphertext element can take, which is given by taking q/2 and * rounding it to the nearest multiple of 3. */ static inline unsigned ciphertext_bias(unsigned q) { return (q/2+1) / 3; } /* * The number of possible values of a ciphertext coefficient (for use * as the m_i in encoding) ranges from +ciphertext_bias(q) to * -ciphertext_bias(q) inclusive. */ static inline unsigned ciphertext_m(unsigned q) { return 1 + 2 * ciphertext_bias(q); } /* * Compute the encoding schedule for a ciphertext. */ static NTRUEncodeSchedule *ntru_encode_ciphertext_schedule( unsigned p, unsigned q) { unsigned m = ciphertext_m(q); uint16_t *ms = snewn(p, uint16_t); for (size_t i = 0; i < p; i++) ms[i] = m; NTRUEncodeSchedule *sched = ntru_encode_schedule(ms, p); sfree(ms); return sched; } /* * Encode a ciphertext. */ void ntru_encode_ciphertext(const uint16_t *ciphertext, unsigned p, unsigned q, BinarySink *bs) { SETUP; /* * Bias the ciphertext, and scale down by 1/3, which we do by * modular multiplication by the inverse of 3 mod q. (That only * works if we know the inputs are all _exact_ multiples of 3 * - but we do!) */ uint16_t *biased_ciphertext = snewn(p, uint16_t); ntru_bias(biased_ciphertext, ciphertext, 3 * ciphertext_bias(q), p, q); ntru_scale(biased_ciphertext, biased_ciphertext, INVERT(3), p, q); /* Encode. */ NTRUEncodeSchedule *sched = ntru_encode_ciphertext_schedule(p, q); ntru_encode(sched, biased_ciphertext, bs); ntru_encode_schedule_free(sched); ring_free(biased_ciphertext, p); } ptrlen ntru_decode_ciphertext(uint16_t *ct, NTRUKeyPair *keypair, BinarySource *src) { unsigned p = keypair->p, q = keypair->q; NTRUEncodeSchedule *sched = ntru_encode_ciphertext_schedule(p, q); /* Retrieve the right number of bytes from the source */ size_t len = ntru_encode_schedule_length(sched); ptrlen encoded = get_data(src, len); if (get_err(src)) { /* As above, return deterministic nonsense on failure */ memset(ct, 0, p*sizeof(*ct)); } else { /* Do the decoding */ ntru_decode(sched, ct, encoded); /* Undo the scaling and bias */ ntru_scale(ct, ct, 3, p, q); ntru_bias(ct, ct, q - 3 * ciphertext_bias(q), p, q); } ntru_encode_schedule_free(sched); return encoded; /* also useful to the caller, optionally */ } /* * Encode a plaintext. * * This is a much simpler encoding than the NTRUEncodeSchedule system: * since elements of a plaintext are mod 3, we just encode each one in * 2 bits, applying the usual bias so that {-1,0,+1} map to {0,1,2} * respectively. * * There's no corresponding decode function, because plaintexts are * never transmitted on the wire (the whole point is that they're too * secret!). Plaintexts are only encoded in order to put them into * hash preimages. */ void ntru_encode_plaintext(const uint16_t *plaintext, unsigned p, BinarySink *bs) { unsigned byte = 0, bitpos = 0; for (size_t i = 0; i < p; i++) { unsigned encoding = (plaintext[i] + 1) * iszero(plaintext[i] >> 1); byte |= encoding << bitpos; bitpos += 2; if (bitpos == 8 || i+1 == p) { put_byte(bs, byte); byte = 0; bitpos = 0; } } } /* ---------------------------------------------------------------------- * Compute the hashes required by the key exchange layer of NTRU Prime. * * There are two of these. The 'confirmation hash' is sent by the * server along with the ciphertext, and the client can recalculate it * to check whether the ciphertext was decrypted correctly. Then, the * 'session hash' is the actual output of key exchange, and if the * confirmation hash doesn't match, it gets deliberately corrupted. */ /* * Make the confirmation hash, whose inputs are the plaintext and the * public key. * * This is defined as H(2 || H(3 || r) || H(4 || K)), where r is the * plaintext and K is the public key (as encoded by the above * functions), and the constants 2,3,4 are single bytes. The choice of * hash function (H itself) is SHA-512 truncated to 256 bits. * * (To be clear: that is _not_ the thing that FIPS 180-4 6.7 defines * as "SHA-512/256", which varies the initialisation vector of the * SHA-512 algorithm as well as truncating the output. _This_ * algorithm uses the standard SHA-512 IV, and _just_ truncates the * output, in the manner suggested by FIPS 180-4 section 7.) * * 'out' should therefore expect to receive 32 bytes of data. */ static void ntru_confirmation_hash( uint8_t *out, const uint16_t *plaintext, const uint16_t *pubkey, unsigned p, unsigned q) { /* The outer hash object */ ssh_hash *hconfirm = ssh_hash_new(&ssh_sha512); put_byte(hconfirm, 2); /* initial byte 2 */ uint8_t hashdata[64]; /* Compute H(3 || r) and add it to the main hash */ ssh_hash *h3r = ssh_hash_new(&ssh_sha512); put_byte(h3r, 3); ntru_encode_plaintext(plaintext, p, BinarySink_UPCAST(h3r)); ssh_hash_final(h3r, hashdata); put_data(hconfirm, hashdata, 32); /* Compute H(4 || K) and add it to the main hash */ ssh_hash *h4K = ssh_hash_new(&ssh_sha512); put_byte(h4K, 4); ntru_encode_pubkey(pubkey, p, q, BinarySink_UPCAST(h4K)); ssh_hash_final(h4K, hashdata); put_data(hconfirm, hashdata, 32); /* Compute the full output of the main SHA-512 hash */ ssh_hash_final(hconfirm, hashdata); /* And copy the first 32 bytes into the caller's output array */ memcpy(out, hashdata, 32); smemclr(hashdata, sizeof(hashdata)); } /* * Make the session hash, whose inputs are the plaintext, the * ciphertext, and the confirmation hash (hence, transitively, a * dependence on the public key as well). * * As computed by the server, and by the client if the confirmation * hash matched, this is defined as * * H(1 || H(3 || r) || ciphertext || confirmation hash) * * but if the confirmation hash _didn't_ match, then the plaintext r * is replaced with the dummy plaintext-shaped value 'rho' we invented * during key generation (presumably to avoid leaking any information * about our secrets), and the initial byte 1 is replaced with 0 (to * ensure that the resulting hash preimage can't match any legitimate * preimage). So in that case, you instead get * * H(0 || H(3 || rho) || ciphertext || confirmation hash) * * The inputs to this function include 'ok', which is the value to use * as the initial byte (1 on success, 0 on failure), and 'plaintext' * which should already have been substituted with rho in case of * failure. * * The ciphertext is provided in already-encoded form. */ static void ntru_session_hash( uint8_t *out, unsigned ok, const uint16_t *plaintext, unsigned p, ptrlen ciphertext, ptrlen confirmation_hash) { /* The outer hash object */ ssh_hash *hsession = ssh_hash_new(&ssh_sha512); put_byte(hsession, ok); /* initial byte 1 or 0 */ uint8_t hashdata[64]; /* Compute H(3 || r), or maybe H(3 || rho), and add it to the main hash */ ssh_hash *h3r = ssh_hash_new(&ssh_sha512); put_byte(h3r, 3); ntru_encode_plaintext(plaintext, p, BinarySink_UPCAST(h3r)); ssh_hash_final(h3r, hashdata); put_data(hsession, hashdata, 32); /* Put the ciphertext and confirmation hash in */ put_datapl(hsession, ciphertext); put_datapl(hsession, confirmation_hash); /* Compute the full output of the main SHA-512 hash */ ssh_hash_final(hsession, hashdata); /* And copy the first 32 bytes into the caller's output array */ memcpy(out, hashdata, 32); smemclr(hashdata, sizeof(hashdata)); } /* ---------------------------------------------------------------------- * Top-level KEM functions. */ /* * The parameters p,q,w for the system. There are other choices of * these, but OpenSSH only specifies this set. (If that ever changes, * we'll need to turn these into elements of the state structures.) */ #define p_LIVE 761 #define q_LIVE 4591 #define w_LIVE 286 struct ntru_dk { NTRUKeyPair *keypair; strbuf *encoded; pq_kem_dk dk; }; static pq_kem_dk *ntru_vt_keygen(const pq_kemalg *alg, BinarySink *ek) { struct ntru_dk *ndk = snew(struct ntru_dk); ndk->dk.vt = alg; ndk->encoded = strbuf_new_nm(); ndk->keypair = ntru_keygen(p_LIVE, q_LIVE, w_LIVE); ntru_encode_pubkey(ndk->keypair->h, p_LIVE, q_LIVE, ek); return &ndk->dk; } static bool ntru_vt_encaps(const pq_kemalg *alg, BinarySink *c, BinarySink *k, ptrlen ek) { BinarySource src[1]; BinarySource_BARE_INIT_PL(src, ek); uint16_t *pubkey = snewn(p_LIVE, uint16_t); ntru_decode_pubkey(pubkey, p_LIVE, q_LIVE, src); if (get_err(src) || get_avail(src)) { /* Hard-fail if the input wasn't exactly the right length */ ring_free(pubkey, p_LIVE); return false; } /* Invent a valid NTRU plaintext. */ uint16_t *plaintext = snewn(p_LIVE, uint16_t); ntru_gen_short(plaintext, p_LIVE, w_LIVE); /* Encrypt the plaintext, and encode the ciphertext into a strbuf, * so we can reuse it for both the session hash and sending to the * client. */ uint16_t *ciphertext = snewn(p_LIVE, uint16_t); ntru_encrypt(ciphertext, plaintext, pubkey, p_LIVE, q_LIVE); strbuf *ciphertext_encoded = strbuf_new_nm(); ntru_encode_ciphertext(ciphertext, p_LIVE, q_LIVE, BinarySink_UPCAST(ciphertext_encoded)); put_datapl(c, ptrlen_from_strbuf(ciphertext_encoded)); /* Compute the confirmation hash, and append that to the data sent * to the other side. */ uint8_t confhash[32]; ntru_confirmation_hash(confhash, plaintext, pubkey, p_LIVE, q_LIVE); put_data(c, confhash, 32); /* Compute the session hash, i.e. the output shared secret. */ uint8_t sesshash[32]; ntru_session_hash(sesshash, 1, plaintext, p_LIVE, ptrlen_from_strbuf(ciphertext_encoded), make_ptrlen(confhash, 32)); put_data(k, sesshash, 32); ring_free(pubkey, p_LIVE); ring_free(plaintext, p_LIVE); ring_free(ciphertext, p_LIVE); strbuf_free(ciphertext_encoded); smemclr(confhash, sizeof(confhash)); smemclr(sesshash, sizeof(sesshash)); return true; } static bool ntru_vt_decaps(pq_kem_dk *dk, BinarySink *k, ptrlen c) { struct ntru_dk *ndk = container_of(dk, struct ntru_dk, dk); /* Expect a string containing a ciphertext and a confirmation hash. */ BinarySource src[1]; BinarySource_BARE_INIT_PL(src, c); uint16_t *ciphertext = snewn(p_LIVE, uint16_t); ptrlen ciphertext_encoded = ntru_decode_ciphertext( ciphertext, ndk->keypair, src); ptrlen confirmation_hash = get_data(src, 32); if (get_err(src) || get_avail(src)) { /* Hard-fail if the input wasn't exactly the right length */ ring_free(ciphertext, p_LIVE); return false; } /* Decrypt the ciphertext to recover the sender's plaintext */ uint16_t *plaintext = snewn(p_LIVE, uint16_t); ntru_decrypt(plaintext, ciphertext, ndk->keypair); /* Make the confirmation hash */ uint8_t confhash[32]; ntru_confirmation_hash(confhash, plaintext, ndk->keypair->h, p_LIVE, q_LIVE); /* Check it matches the one the server sent */ unsigned ok = smemeq(confhash, confirmation_hash.ptr, 32); /* If not, substitute in rho for the plaintext in the session hash */ unsigned mask = ok-1; for (size_t i = 0; i < p_LIVE; i++) plaintext[i] ^= mask & (plaintext[i] ^ ndk->keypair->rho[i]); /* Compute the session hash, whether or not we did that */ uint8_t sesshash[32]; ntru_session_hash(sesshash, ok, plaintext, p_LIVE, ciphertext_encoded, confirmation_hash); put_data(k, sesshash, 32); ring_free(plaintext, p_LIVE); ring_free(ciphertext, p_LIVE); smemclr(confhash, sizeof(confhash)); smemclr(sesshash, sizeof(sesshash)); return true; } static void ntru_vt_free_dk(pq_kem_dk *dk) { struct ntru_dk *ndk = container_of(dk, struct ntru_dk, dk); strbuf_free(ndk->encoded); ntru_keypair_free(ndk->keypair); sfree(ndk); } const pq_kemalg ssh_ntru = { .keygen = ntru_vt_keygen, .encaps = ntru_vt_encaps, .decaps = ntru_vt_decaps, .free_dk = ntru_vt_free_dk, .description = "NTRU Prime", .ek_len = 1158, .c_len = 1039, };