mirror of
https://github.com/henrydcase/pqc.git
synced 2024-11-23 16:08:59 +00:00
56a0fcb135
* Copied qTESLA-p-I round2 (2019-08-19) code * Code compiles, NIST-KAT works * Included detached signature API * Generated testvectors * Fixed name in api.h * code style * Fixed error in Makefile * Passing pytest * Fixing types (uint8_t bytes and size_t indices) * Replaced SHAKE with SHAKE128 where necessary * Fixed bug: (signed) integer overflow * Added qTESLA-p-III * Code is now independent of machine endianness * repaired Microsoft makefile
404 lines
15 KiB
C
404 lines
15 KiB
C
/*************************************************************************************
|
|
* qTESLA: an efficient post-quantum signature scheme based on the R-LWE problem
|
|
*
|
|
* Abstract: high-level functions of the signature scheme
|
|
**************************************************************************************/
|
|
|
|
#include "api.h"
|
|
#include "fips202.h"
|
|
#include "gauss.h"
|
|
#include "pack.h"
|
|
#include "params.h"
|
|
#include "poly.h"
|
|
#include "randombytes.h"
|
|
#include "sample.h"
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
static void hash_H(uint8_t *c_bin, const poly_k v, const uint8_t *hm) {
|
|
// Hash-based function H to generate c'
|
|
uint8_t t[PARAM_K * PARAM_N + HM_BYTES];
|
|
int32_t mask, cL, temp;
|
|
size_t i, k, index;
|
|
|
|
for (k = 0; k < PARAM_K; k++) {
|
|
index = k * PARAM_N;
|
|
for (i = 0; i < PARAM_N; i++) {
|
|
temp = (int32_t)v[index];
|
|
// If v[i] > PARAM_Q/2 then v[i] -= PARAM_Q
|
|
mask = (PARAM_Q / 2 - temp) >> (RADIX32 - 1);
|
|
temp = ((temp - PARAM_Q) & mask) | (temp & ~mask);
|
|
|
|
cL = temp & ((1 << PARAM_D) - 1);
|
|
// If cL > 2^(d-1) then cL -= 2^d
|
|
mask = ((1 << (PARAM_D - 1)) - cL) >> (RADIX32 - 1);
|
|
cL = ((cL - (1 << PARAM_D)) & mask) | (cL & ~mask);
|
|
t[index++] = (uint8_t)((temp - cL) >> PARAM_D);
|
|
}
|
|
}
|
|
memcpy(&t[PARAM_K * PARAM_N], hm, HM_BYTES);
|
|
SHAKE(c_bin, CRYPTO_C_BYTES, t, PARAM_K * PARAM_N + HM_BYTES);
|
|
}
|
|
|
|
|
|
static inline int32_t Abs(int32_t value) {
|
|
// Compute absolute value
|
|
|
|
int32_t mask = value >> (RADIX32 - 1);
|
|
return (mask ^ value) - mask;
|
|
}
|
|
|
|
|
|
static int test_rejection(const poly z) {
|
|
// Check bounds for signature vector z during signing. Returns 0 if valid, otherwise outputs 1 if invalid (rejected).
|
|
// This function does not leak any information about the coefficient that fails the test.
|
|
uint32_t valid = 0;
|
|
|
|
for (size_t i = 0; i < PARAM_N; i++) {
|
|
valid |= (PARAM_B - PARAM_S) - (uint32_t)Abs((int32_t)z[i]);
|
|
}
|
|
return (int)(valid >> 31);
|
|
}
|
|
|
|
|
|
static int test_correctness(const poly v) {
|
|
// Check bounds for w = v - ec during signature verification. Returns 0 if valid, otherwise outputs 1 if invalid (rejected).
|
|
// This function leaks the position of the coefficient that fails the test (but this is independent of the secret data).
|
|
// It does not leak the sign of the coefficients.
|
|
int32_t mask, left, val;
|
|
uint32_t t0, t1;
|
|
|
|
for (size_t i = 0; i < PARAM_N; i++) {
|
|
// If v[i] > PARAM_Q/2 then v[i] -= PARAM_Q
|
|
mask = (int32_t)(PARAM_Q / 2 - v[i]) >> (RADIX32 - 1);
|
|
val = (int32_t)(((v[i] - PARAM_Q) & mask) | (v[i] & ~mask));
|
|
// If (Abs(val) < PARAM_Q/2 - PARAM_E) then t0 = 0, else t0 = 1
|
|
t0 = (uint32_t)(~(Abs(val) - (PARAM_Q / 2 - PARAM_E))) >> (RADIX32 - 1);
|
|
|
|
left = val;
|
|
val = (val + (1 << (PARAM_D - 1)) - 1) >> PARAM_D;
|
|
val = left - (val << PARAM_D);
|
|
// If (Abs(val) < (1<<(PARAM_D-1))-PARAM_E) then t1 = 0, else t1 = 1
|
|
t1 = (uint32_t)(~(Abs(val) - ((1 << (PARAM_D - 1)) - PARAM_E))) >> (RADIX32 - 1);
|
|
|
|
if ((t0 | t1) == 1) { // Returns 1 if any of the two tests failed
|
|
return 1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int test_z(const poly z) {
|
|
// Check bounds for signature vector z during signature verification
|
|
// Returns 0 if valid, otherwise outputs 1 if invalid (rejected)
|
|
|
|
for (size_t i = 0; i < PARAM_N; i++) {
|
|
if (z[i] < -(PARAM_B - PARAM_S) || z[i] > (PARAM_B - PARAM_S)) {
|
|
return 1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int check_ES(poly p, unsigned int bound) {
|
|
// Checks the generated polynomial e or s
|
|
// Returns 0 if ok, otherwise returns 1
|
|
unsigned int sum = 0;
|
|
size_t i, j, limit = PARAM_N;
|
|
int32_t temp, mask, list[PARAM_N];
|
|
|
|
for (j = 0; j < PARAM_N; j++) {
|
|
list[j] = Abs((int32_t)p[j]);
|
|
}
|
|
|
|
for (j = 0; j < PARAM_H; j++) {
|
|
for (i = 0; i < limit - 1; i++) {
|
|
// If list[i+1] > list[i] then exchange contents
|
|
mask = (list[i + 1] - list[i]) >> (RADIX32 - 1);
|
|
temp = (list[i + 1] & mask) | (list[i] & ~mask);
|
|
list[i + 1] = (list[i] & mask) | (list[i + 1] & ~mask);
|
|
list[i] = temp;
|
|
}
|
|
sum += (unsigned int)list[limit - 1];
|
|
limit -= 1;
|
|
}
|
|
|
|
if (sum > bound) {
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*********************************************************
|
|
* Name: crypto_sign_keypair
|
|
* Description: generates a public and private key pair
|
|
* Parameters: inputs: none
|
|
* outputs:
|
|
* - uint8_t *pk: public key
|
|
* - uint8_t *sk: secret key
|
|
* Returns: 0 for successful execution
|
|
**********************************************************/
|
|
int PQCLEAN_QTESLAPI_CLEAN_crypto_sign_keypair(uint8_t *pk, uint8_t *sk) {
|
|
uint8_t randomness[CRYPTO_RANDOMBYTES], randomness_extended[(PARAM_K + 3)*CRYPTO_SEEDBYTES];
|
|
poly s, s_ntt;
|
|
poly_k e, a, t;
|
|
size_t k; // Initialize domain separator for error and secret polynomials
|
|
uint16_t nonce = 0;
|
|
|
|
// Get randomness_extended <- seed_e, seed_s, seed_a, seed_y
|
|
randombytes(randomness, CRYPTO_RANDOMBYTES);
|
|
SHAKE(randomness_extended, (PARAM_K + 3)*CRYPTO_SEEDBYTES, randomness, CRYPTO_RANDOMBYTES);
|
|
|
|
for (k = 0; k < PARAM_K; k++) {
|
|
do { // Sample the error polynomials
|
|
PQCLEAN_QTESLAPI_CLEAN_sample_gauss_poly(&e[k * PARAM_N], &randomness_extended[k * CRYPTO_SEEDBYTES], ++nonce);
|
|
} while (check_ES(&e[k * PARAM_N], PARAM_KEYGEN_BOUND_E) != 0);
|
|
}
|
|
|
|
do { // Sample the secret polynomial
|
|
PQCLEAN_QTESLAPI_CLEAN_sample_gauss_poly(s, &randomness_extended[PARAM_K * CRYPTO_SEEDBYTES], ++nonce);
|
|
} while (check_ES(s, PARAM_KEYGEN_BOUND_S) != 0);
|
|
|
|
// Generate uniform polynomial "a"
|
|
PQCLEAN_QTESLAPI_CLEAN_poly_uniform(a, &randomness_extended[(PARAM_K + 1)*CRYPTO_SEEDBYTES]);
|
|
PQCLEAN_QTESLAPI_CLEAN_poly_ntt(s_ntt, s);
|
|
|
|
// Compute the public key t = as+e
|
|
for (k = 0; k < PARAM_K; k++) {
|
|
PQCLEAN_QTESLAPI_CLEAN_poly_mul(&t[k * PARAM_N], &a[k * PARAM_N], s_ntt);
|
|
PQCLEAN_QTESLAPI_CLEAN_poly_add_correct(&t[k * PARAM_N], &t[k * PARAM_N], &e[k * PARAM_N]);
|
|
}
|
|
|
|
// Pack public and private keys
|
|
PQCLEAN_QTESLAPI_CLEAN_pack_sk(sk, s, e, &randomness_extended[(PARAM_K + 1)*CRYPTO_SEEDBYTES]);
|
|
PQCLEAN_QTESLAPI_CLEAN_encode_pk(pk, t, &randomness_extended[(PARAM_K + 1)*CRYPTO_SEEDBYTES]);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/***************************************************************
|
|
* Name: crypto_sign
|
|
* Description: outputs a signature for a given message m
|
|
* Parameters: inputs:
|
|
* - const uint8_t *m: message to be signed
|
|
* - size_t mlen: message length
|
|
* - const uint8_t* sk: secret key
|
|
* outputs:
|
|
* - uint8_t *sm: signature
|
|
* - size_t *smlen: signature length*
|
|
* Returns: 0 for successful execution
|
|
***************************************************************/
|
|
int PQCLEAN_QTESLAPI_CLEAN_crypto_sign(uint8_t *sm, size_t *smlen, const uint8_t *m, size_t mlen, const uint8_t *sk) {
|
|
uint8_t c[CRYPTO_C_BYTES], randomness[CRYPTO_SEEDBYTES], randomness_input[CRYPTO_RANDOMBYTES + CRYPTO_SEEDBYTES + HM_BYTES];
|
|
uint32_t pos_list[PARAM_H];
|
|
int16_t sign_list[PARAM_H];
|
|
poly y, y_ntt, Sc, z;
|
|
poly_k v, Ec, a;
|
|
size_t k;
|
|
int rsp;
|
|
uint16_t nonce = 0; // Initialize domain separator for sampling y
|
|
|
|
// Get H(seed_y, r, H(m)) to sample y
|
|
randombytes(randomness_input + CRYPTO_RANDOMBYTES, CRYPTO_RANDOMBYTES);
|
|
memcpy(randomness_input, &sk[PQCLEAN_QTESLAPI_CLEAN_CRYPTO_SECRETKEYBYTES - CRYPTO_SEEDBYTES], CRYPTO_SEEDBYTES);
|
|
SHAKE(randomness_input + CRYPTO_RANDOMBYTES + CRYPTO_SEEDBYTES, HM_BYTES, m, mlen);
|
|
SHAKE(randomness, CRYPTO_SEEDBYTES, randomness_input, CRYPTO_RANDOMBYTES + CRYPTO_SEEDBYTES + HM_BYTES);
|
|
|
|
PQCLEAN_QTESLAPI_CLEAN_poly_uniform(a, &sk[PQCLEAN_QTESLAPI_CLEAN_CRYPTO_SECRETKEYBYTES - 2 * CRYPTO_SEEDBYTES]);
|
|
|
|
while (1) {
|
|
PQCLEAN_QTESLAPI_CLEAN_sample_y(y, randomness, ++nonce); // Sample y uniformly at random from [-B,B]
|
|
PQCLEAN_QTESLAPI_CLEAN_poly_ntt (y_ntt, y);
|
|
for (k = 0; k < PARAM_K; k++) {
|
|
PQCLEAN_QTESLAPI_CLEAN_poly_mul(&v[k * PARAM_N], &a[k * PARAM_N], y_ntt);
|
|
}
|
|
hash_H(c, v, randomness_input + CRYPTO_RANDOMBYTES + CRYPTO_SEEDBYTES);
|
|
PQCLEAN_QTESLAPI_CLEAN_encode_c(pos_list, sign_list, c); // Generate c = Enc(c'), where c' is the hashing of v together with m
|
|
PQCLEAN_QTESLAPI_CLEAN_sparse_mul8(Sc, sk, pos_list, sign_list);
|
|
PQCLEAN_QTESLAPI_CLEAN_poly_add(z, y, Sc); // Compute z = y + sc
|
|
|
|
if (test_rejection(z) != 0) { // Rejection sampling
|
|
continue;
|
|
}
|
|
|
|
for (k = 0; k < PARAM_K; k++) {
|
|
PQCLEAN_QTESLAPI_CLEAN_sparse_mul8(&Ec[k * PARAM_N], sk + (sizeof(int8_t)*PARAM_N * (k + 1)), pos_list, sign_list);
|
|
PQCLEAN_QTESLAPI_CLEAN_poly_sub(&v[k * PARAM_N], &v[k * PARAM_N], &Ec[k * PARAM_N]);
|
|
rsp = test_correctness(&v[k * PARAM_N]);
|
|
if (rsp != 0) {
|
|
break;
|
|
}
|
|
}
|
|
if (rsp != 0) {
|
|
continue;
|
|
}
|
|
|
|
// Copy message to signature package, and pack signature
|
|
for (size_t i = 0; i < mlen; i++) {
|
|
sm[PQCLEAN_QTESLAPI_CLEAN_CRYPTO_BYTES + i] = m[i];
|
|
}
|
|
*smlen = PQCLEAN_QTESLAPI_CLEAN_CRYPTO_BYTES + mlen;
|
|
PQCLEAN_QTESLAPI_CLEAN_encode_sig(sm, c, z);
|
|
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
|
|
/************************************************************
|
|
* Name: crypto_sign_open
|
|
* Description: verification of a signature sm
|
|
* Parameters: inputs:
|
|
* - const uint8_t *sm: signature
|
|
* - size_t smlen: signature length
|
|
* - const uint8_t* pk: public Key
|
|
* outputs:
|
|
* - uint8_t *m: original (signed) message
|
|
* - size_t *mlen: message length*
|
|
* Returns: 0 for valid signature
|
|
* <0 for invalid signature
|
|
************************************************************/
|
|
int PQCLEAN_QTESLAPI_CLEAN_crypto_sign_open(uint8_t *m, size_t *mlen, const uint8_t *sm, size_t smlen, const uint8_t *pk) {
|
|
uint8_t c[CRYPTO_C_BYTES], c_sig[CRYPTO_C_BYTES], seed[CRYPTO_SEEDBYTES], hm[HM_BYTES];
|
|
uint32_t pos_list[PARAM_H];
|
|
int16_t sign_list[PARAM_H];
|
|
int32_t pk_t[PARAM_N * PARAM_K];
|
|
poly_k w, a, Tc;
|
|
poly z, z_ntt;
|
|
size_t k;
|
|
|
|
if (smlen < PQCLEAN_QTESLAPI_CLEAN_CRYPTO_BYTES) {
|
|
return -1;
|
|
}
|
|
|
|
PQCLEAN_QTESLAPI_CLEAN_decode_sig(c, z, sm);
|
|
if (test_z(z) != 0) {
|
|
return -2; // Check norm of z
|
|
}
|
|
PQCLEAN_QTESLAPI_CLEAN_decode_pk(pk_t, seed, pk);
|
|
PQCLEAN_QTESLAPI_CLEAN_poly_uniform(a, seed);
|
|
PQCLEAN_QTESLAPI_CLEAN_encode_c(pos_list, sign_list, c);
|
|
PQCLEAN_QTESLAPI_CLEAN_poly_ntt(z_ntt, z);
|
|
|
|
for (k = 0; k < PARAM_K; k++) { // Compute w = az - tc
|
|
PQCLEAN_QTESLAPI_CLEAN_sparse_mul32(&Tc[k * PARAM_N], &pk_t[k * PARAM_N], pos_list, sign_list);
|
|
PQCLEAN_QTESLAPI_CLEAN_poly_mul(&w[k * PARAM_N], &a[k * PARAM_N], z_ntt);
|
|
PQCLEAN_QTESLAPI_CLEAN_poly_sub(&w[k * PARAM_N], &w[k * PARAM_N], &Tc[k * PARAM_N]);
|
|
}
|
|
SHAKE(hm, HM_BYTES, sm + PQCLEAN_QTESLAPI_CLEAN_CRYPTO_BYTES, smlen - PQCLEAN_QTESLAPI_CLEAN_CRYPTO_BYTES);
|
|
hash_H(c_sig, w, hm);
|
|
|
|
// Check if the calculated c matches c from the signature
|
|
if (memcmp(c, c_sig, CRYPTO_C_BYTES) != 0) {
|
|
return -3;
|
|
}
|
|
|
|
*mlen = smlen - PQCLEAN_QTESLAPI_CLEAN_CRYPTO_BYTES;
|
|
for (size_t i = 0; i < *mlen; i++) {
|
|
m[i] = sm[PQCLEAN_QTESLAPI_CLEAN_CRYPTO_BYTES + i];
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int PQCLEAN_QTESLAPI_CLEAN_crypto_sign_signature(
|
|
uint8_t *sig, size_t *siglen,
|
|
const uint8_t *m, size_t mlen,
|
|
const uint8_t *sk) {
|
|
uint8_t c[CRYPTO_C_BYTES], randomness[CRYPTO_SEEDBYTES], randomness_input[CRYPTO_RANDOMBYTES + CRYPTO_SEEDBYTES + HM_BYTES];
|
|
uint32_t pos_list[PARAM_H];
|
|
int16_t sign_list[PARAM_H];
|
|
poly y, y_ntt, Sc, z;
|
|
poly_k v, Ec, a;
|
|
size_t k;
|
|
int rsp;
|
|
uint16_t nonce = 0; // Initialize domain separator for sampling y
|
|
|
|
// Get H(seed_y, r, H(m)) to sample y
|
|
randombytes(randomness_input + CRYPTO_RANDOMBYTES, CRYPTO_RANDOMBYTES);
|
|
memcpy(randomness_input, &sk[PQCLEAN_QTESLAPI_CLEAN_CRYPTO_SECRETKEYBYTES - CRYPTO_SEEDBYTES], CRYPTO_SEEDBYTES);
|
|
SHAKE(randomness_input + CRYPTO_RANDOMBYTES + CRYPTO_SEEDBYTES, HM_BYTES, m, mlen);
|
|
SHAKE(randomness, CRYPTO_SEEDBYTES, randomness_input, CRYPTO_RANDOMBYTES + CRYPTO_SEEDBYTES + HM_BYTES);
|
|
|
|
PQCLEAN_QTESLAPI_CLEAN_poly_uniform(a, &sk[PQCLEAN_QTESLAPI_CLEAN_CRYPTO_SECRETKEYBYTES - 2 * CRYPTO_SEEDBYTES]);
|
|
|
|
while (1) {
|
|
PQCLEAN_QTESLAPI_CLEAN_sample_y(y, randomness, ++nonce); // Sample y uniformly at random from [-B,B]
|
|
PQCLEAN_QTESLAPI_CLEAN_poly_ntt (y_ntt, y);
|
|
for (k = 0; k < PARAM_K; k++) {
|
|
PQCLEAN_QTESLAPI_CLEAN_poly_mul(&v[k * PARAM_N], &a[k * PARAM_N], y_ntt);
|
|
}
|
|
hash_H(c, v, randomness_input + CRYPTO_RANDOMBYTES + CRYPTO_SEEDBYTES);
|
|
PQCLEAN_QTESLAPI_CLEAN_encode_c(pos_list, sign_list, c); // Generate c = Enc(c'), where c' is the hashing of v together with m
|
|
PQCLEAN_QTESLAPI_CLEAN_sparse_mul8(Sc, sk, pos_list, sign_list);
|
|
PQCLEAN_QTESLAPI_CLEAN_poly_add(z, y, Sc); // Compute z = y + sc
|
|
|
|
if (test_rejection(z) != 0) { // Rejection sampling
|
|
continue;
|
|
}
|
|
|
|
for (k = 0; k < PARAM_K; k++) {
|
|
PQCLEAN_QTESLAPI_CLEAN_sparse_mul8(&Ec[k * PARAM_N], sk + (sizeof(int8_t)*PARAM_N * (k + 1)), pos_list, sign_list);
|
|
PQCLEAN_QTESLAPI_CLEAN_poly_sub(&v[k * PARAM_N], &v[k * PARAM_N], &Ec[k * PARAM_N]);
|
|
rsp = test_correctness(&v[k * PARAM_N]);
|
|
if (rsp != 0) {
|
|
break;
|
|
}
|
|
}
|
|
if (rsp != 0) {
|
|
continue;
|
|
}
|
|
|
|
// pack signature
|
|
*siglen = PQCLEAN_QTESLAPI_CLEAN_CRYPTO_BYTES;
|
|
PQCLEAN_QTESLAPI_CLEAN_encode_sig(sig, c, z);
|
|
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
int PQCLEAN_QTESLAPI_CLEAN_crypto_sign_verify(
|
|
const uint8_t *sig, size_t siglen,
|
|
const uint8_t *m, size_t mlen,
|
|
const uint8_t *pk) {
|
|
uint8_t c[CRYPTO_C_BYTES], c_sig[CRYPTO_C_BYTES], seed[CRYPTO_SEEDBYTES], hm[HM_BYTES];
|
|
uint32_t pos_list[PARAM_H];
|
|
int16_t sign_list[PARAM_H];
|
|
int32_t pk_t[PARAM_N * PARAM_K];
|
|
poly_k w, a, Tc;
|
|
poly z, z_ntt;
|
|
size_t k;
|
|
|
|
if (siglen < PQCLEAN_QTESLAPI_CLEAN_CRYPTO_BYTES) {
|
|
return -1;
|
|
}
|
|
|
|
PQCLEAN_QTESLAPI_CLEAN_decode_sig(c, z, sig);
|
|
if (test_z(z) != 0) {
|
|
return -2; // Check norm of z
|
|
}
|
|
PQCLEAN_QTESLAPI_CLEAN_decode_pk(pk_t, seed, pk);
|
|
PQCLEAN_QTESLAPI_CLEAN_poly_uniform(a, seed);
|
|
PQCLEAN_QTESLAPI_CLEAN_encode_c(pos_list, sign_list, c);
|
|
PQCLEAN_QTESLAPI_CLEAN_poly_ntt(z_ntt, z);
|
|
|
|
for (k = 0; k < PARAM_K; k++) { // Compute w = az - tc
|
|
PQCLEAN_QTESLAPI_CLEAN_sparse_mul32(&Tc[k * PARAM_N], &pk_t[k * PARAM_N], pos_list, sign_list);
|
|
PQCLEAN_QTESLAPI_CLEAN_poly_mul(&w[k * PARAM_N], &a[k * PARAM_N], z_ntt);
|
|
PQCLEAN_QTESLAPI_CLEAN_poly_sub(&w[k * PARAM_N], &w[k * PARAM_N], &Tc[k * PARAM_N]);
|
|
}
|
|
SHAKE(hm, HM_BYTES, m, mlen);
|
|
hash_H(c_sig, w, hm);
|
|
|
|
// Check if the calculated c matches c from the signature
|
|
if (memcmp(c, c_sig, CRYPTO_C_BYTES) != 0) {
|
|
return -3;
|
|
}
|
|
|
|
return 0;
|
|
}
|