- For any object
`x`, we write`len(x)`to denote its length in bytes. - For two byte arrays
`x`and`y`, write`x || y`to denote their concatenation. - I2OSP(x, xLen): Converts a non-negative integer
`x`into a byte array of specified length`xLen`as described in {{!RFC8017}}. Note that this function returns a byte array in big-endian byte order. `n`and`q`are coprime positive integers. The first defines the size of the polynomials (treated as a zero indexed arrays), and the latter refers to the modulus.

- Type: HPS
- N: 509
- Q: 2048
- Hash: SHA3-256.
- ID: 0x0001

- Type: HPS
- N: 677
- Q: 2048
- Hash: SHA3-256.
- ID: 0x0002

- Type: HPS
- N: 821
- Q: 4096
- Hash: SHA3-256.
- ID: 0x0003

- Generate a random polynomial f using using the
`sample_iid`procedure. - Generate a random polynomial g using the the
`sample_fixed_type`procedure - Multiply each coefficient of g by 3.
- Compute FG_inv = Inverse( f * g mod q) mod q.
- Compute H = FG_inv * g * g (modulo q)
- Compute H_inv = FG_inv * f * f (modulo q)
- Compute F_inv = Inverse( f ) (this computation is done modulo 3)
- Generate a random 32 byte value s randomly

- Unpack the public key (using the unpack_Rq0 procedure) to obtain the polynomial H
- Sample a random R using the sample_iid procedure
- Sample a random M using the sample_fixed_type procedure
- Compute C = R*H + M (perfoming both the polynomial multiplication and polynomial addition modulo q)
- Serialize both R and M (using the pack_S3 procedure on both) and use SHA3-256 to hash the concatination; the resulting 32 bytes is the secret string K
- Return K and C serialized using the pack_Rq0 procedure

Unpack the ciphertext (using the unpack_Rq0 procedure) to obtain the polynomial C Compute A = C*F (modulo q) For each coeffient x in A, if it is < q/2, replace it with x mod 3; if it is >= q/2, replace it with 2 - (q-1-x) mod 3

Compute M = A*F_inv (modulo 3) - note the change of moduli For each 2 coefficient within M, replace it with q-1 Compute R = (C - M) * H_inv (modulo q) Compute R = modPhiN(R) (modulo q) Set Success = ValidM(M) AND ValidR(R) Serialize both R and M (using the pack_S3 procedure on both) and use SHA3-256 to hash the concatination; the resulting 32 bytes is K1 Use SHA3-256 to hash the concatination of S (from the private key) and C, the resulting 32 bytes is K2 If Success, return K=K1; otherwise, return K=K2

Parameter Set | Polynomial Size N | Modulus Q |
---|---|---|

ntruhps2048509 | 509 | 2048 |

ntruhps2048677 | 677 | 2048 |

ntruhps4096821 | 821 | 4096 |

Step 1: Alice follows the 'Private and Public Key Generation' procedure; this creates a private key (which Alice keeps to herself) and a public key, which she sends to Bob. Alternatively, she may decide to reuse a previously generated keypair. Step 2: Bob receives Alice's public key, and follows the 'Key Encapsulation' procedure; this creates a secret string (which Bob keeps to himself) and a ciphertext, which he sends to Alice Step 3: Alice recieves Bob's ciphertext, and follows the 'Key Decapuslation' procedure; this creates a secret string (which Alice keeps to herself). Alice can then either destroy her private key, or keep it around for next time.

Parameter Set | Security Model | Bit Strength |
---|---|---|

ntruhps2048509 | IND-CCA2 | 128 |

ntruhps2048677 | IND-CCA2 | 192 |

ntruhps4096821 | IND-CCA2 | 256 |

HRSS - currently, we omit that parameter set - it does perform slightly faster than the HPS parameter set at the same security level (at the cost of a larger public key/ciphertext). My expectation is that the larger keyshare size for HRSS is a more significant cost than the larger computational cost for HPS. It would also complicate the logic somewhat (as we would need to specify both the HPS and the HRSS ways of doing things). Is the decision to leave it out the correct one? We don't specify a flattened format for a private key. In my view, there is no need; systems will generally use ephemerial public/private key pairs, that is, create them on the fly, use them for one or a handful of exchanges and then throw them away. In this use case, there is no need to transfer a private key to another device. Now, it is possible for NTRU to be used with static keys - should we try to address that case? There is a tiny chance of failure during key generation (if F happens to be selected as all 0); this happens with probability < 2^-800 (that is, it'll never happen in practice, unless the random number generator broke). When this happens, the computation of the inverse of F will fail; what happens in that case would depend on the inverter implementation. Should we ignore it or address it? It appears that the parameter set HPS4096821 was added lately to the NTRU definition, and did not undergo the same vetting that the other parameter sets did. As such, it is unclear whether that parameter set gives the claimed level of security. Should we remove it from this RFC, or just add some warning text within the security considerations?