pqc/crypto_kem/papabear/clean/threebears.c
2021-03-24 21:02:44 +00:00

226 lines
7.7 KiB
C

/** ThreeBears reference implementation */
#include "api.h"
#include "melas_fec.h"
#include "params.h"
#include "ring.h"
#include "sp800-185.h"
#include "threebears.h"
#define FEC_BYTES ((FEC_BITS+7)/8)
#define ENC_BITS (ENC_SEED_BYTES*8 + FEC_BITS)
enum { HASH_PURPOSE_UNIFORM = 0, HASH_PURPOSE_KEYGEN = 1, HASH_PURPOSE_ENCAPS = 2, HASH_PURPOSE_PRF = 3 };
/** Initialize the hash function with a given purpose */
static void threebears_hash_init(
shake256incctx *ctx,
uint8_t purpose
) {
const unsigned char S[] = "ThreeBears";
const uint8_t pblock[15] = {
VERSION, PRIVATE_KEY_BYTES, MATRIX_SEED_BYTES, ENC_SEED_BYTES,
IV_BYTES, SHARED_SECRET_BYTES, LGX, DIGITS & 0xFF, DIGITS >> 8, DIM,
VAR_TIMES_128 - 1, LPR_BITS, FEC_BITS, CCA, 0 /* padding */
};
cshake256_inc_init(ctx, NULL, 0, (const uint8_t *)S, sizeof(S) - 1);
cshake256_inc_absorb(ctx, (const uint8_t *)pblock, sizeof(pblock));
cshake256_inc_absorb(ctx, &purpose, 1);
}
/** Sample n gf_t's uniformly from a seed */
static void uniform(gf_t matrix, const uint8_t *seed, uint8_t iv) {
uint8_t c[GF_BYTES];
shake256incctx ctx;
threebears_hash_init(&ctx, HASH_PURPOSE_UNIFORM);
cshake256_inc_absorb(&ctx, seed, MATRIX_SEED_BYTES);
cshake256_inc_absorb(&ctx, &iv, 1);
cshake256_inc_finalize(&ctx);
cshake256_inc_squeeze(c, sizeof(c), &ctx);
PQCLEAN_PAPABEAR_CLEAN_expand(matrix, c);
}
/** The ThreeBears error distribution */
static slimb_t psi(uint8_t ci) {
int sample = 0, var = VAR_TIMES_128;
for (; var > 64; var -= 64, ci = (uint8_t)(ci << 2)) {
sample += ((ci + 64) >> 8) + ((ci - 64) >> 8);
}
return (slimb_t)(sample + ((ci + var) >> 8) + ((ci - var) >> 8));
}
/** Sample a vector of n noise elements */
static void noise(gf_t x, const shake256incctx *ctx, uint8_t iv) {
uint8_t c[DIGITS];
shake256incctx ctx2;
memcpy(&ctx2, ctx, sizeof(ctx2));
cshake256_inc_absorb(&ctx2, &iv, 1);
cshake256_inc_finalize(&ctx2);
cshake256_inc_squeeze(c, DIGITS, &ctx2);
for (size_t i = 0; i < DIGITS; i++) {
x[i] = (limb_t)(psi(c[i]) + modulus(i));
}
}
/* Expand public key from private key */
void PQCLEAN_PAPABEAR_CLEAN_get_pubkey(uint8_t *pk, const uint8_t *sk) {
shake256incctx ctx;
shake256incctx ctx2;
gf_t sk_expanded[DIM], b, c;
threebears_hash_init(&ctx, HASH_PURPOSE_KEYGEN);
cshake256_inc_absorb(&ctx, sk, PRIVATE_KEY_BYTES);
memcpy(&ctx2, &ctx, sizeof(ctx2));
cshake256_inc_finalize(&ctx2);
cshake256_inc_squeeze(pk, MATRIX_SEED_BYTES, &ctx2);
for (uint8_t i = 0; i < DIM; i++) {
noise(sk_expanded[i], &ctx, i);
}
for (uint8_t i = 0; i < DIM; i++) {
noise(c, &ctx, (uint8_t)(i + DIM));
for (uint8_t j = 0; j < DIM; j++) {
uniform(b, pk, (uint8_t) (i + DIM * j));
PQCLEAN_PAPABEAR_CLEAN_mac(c, b, sk_expanded[j]);
}
PQCLEAN_PAPABEAR_CLEAN_contract(&pk[MATRIX_SEED_BYTES + i * GF_BYTES], c);
}
}
/* Encapsulate a shared secret and return it */
void PQCLEAN_PAPABEAR_CLEAN_encapsulate(
uint8_t *shared_secret,
uint8_t *capsule,
const uint8_t *pk,
const uint8_t *seed
) {
uint8_t *lpr_data = &capsule[GF_BYTES * DIM];
shake256incctx ctx;
gf_t sk_expanded[DIM], b, c;
uint8_t tbi[ENC_SEED_BYTES + FEC_BYTES];
dlimb_t rlimb0, rlimb1;
limb_t h;
uint8_t *iv = &lpr_data[(ENC_BITS * LPR_BITS + 7) / 8];
memcpy(iv, &seed[ENC_SEED_BYTES], IV_BYTES);
threebears_hash_init(&ctx, HASH_PURPOSE_ENCAPS);
cshake256_inc_absorb(&ctx, pk, MATRIX_SEED_BYTES);
cshake256_inc_absorb(&ctx, seed, ENC_SEED_BYTES + IV_BYTES);
for (uint8_t i = 0; i < DIM; i++) {
noise(sk_expanded[i], &ctx, i);
}
for (uint8_t i = 0; i < DIM; i++) {
noise(c, &ctx, (uint8_t)(i + DIM));
for (uint8_t j = 0; j < DIM; j++) {
uniform(b, pk, (uint8_t)(j + DIM * i));
PQCLEAN_PAPABEAR_CLEAN_mac(c, b, sk_expanded[j]);
}
PQCLEAN_PAPABEAR_CLEAN_contract(&capsule[i * GF_BYTES], c);
}
noise(c, &ctx, (uint8_t)(2 * DIM));
/* Calculate approximate shared secret */
for (uint8_t i = 0; i < DIM; i++) {
PQCLEAN_PAPABEAR_CLEAN_expand(b, &pk[MATRIX_SEED_BYTES + i * GF_BYTES]);
PQCLEAN_PAPABEAR_CLEAN_mac(c, b, sk_expanded[i]);
}
PQCLEAN_PAPABEAR_CLEAN_canon(c);
memcpy(tbi, seed, ENC_SEED_BYTES);
PQCLEAN_PAPABEAR_CLEAN_melas_fec_set(&tbi[ENC_SEED_BYTES], tbi, ENC_SEED_BYTES);
/* Export with rounding */
for (size_t i = 0; i < ENC_BITS; i += 2) {
h = (limb_t)(tbi[i / 8] >> (i % 8));
rlimb0 = (dlimb_t)((c[i / 2] >> (LGX - LPR_BITS)) + (h << 3));
rlimb1 = (dlimb_t)((c[DIGITS - i / 2 - 1] >> (LGX - LPR_BITS)) + ((h >> 1) << 3));
lpr_data[i / 2] = (uint8_t)((rlimb0 & 0xF) | rlimb1 << 4);
}
cshake256_inc_finalize(&ctx);
cshake256_inc_squeeze(shared_secret, SHARED_SECRET_BYTES, &ctx);
}
/* Decapsulate a shared secret and return it */
void PQCLEAN_PAPABEAR_CLEAN_decapsulate(
uint8_t shared_secret[SHARED_SECRET_BYTES],
const uint8_t capsule[CAPSULE_BYTES],
const uint8_t sk[PRIVATE_KEY_BYTES]
) {
const uint8_t *lpr_data = &capsule[GF_BYTES * DIM];
shake256incctx ctx;
gf_t ska, b, c = {0};
uint8_t seed[ENC_SEED_BYTES + FEC_BYTES + IV_BYTES];
limb_t rounding, out;
size_t j;
limb_t our_rlimb, their_rlimb, delta;
uint8_t pk[PUBLIC_KEY_BYTES], capsule2[CAPSULE_BYTES];
uint8_t ret, ok, sep, prfk[PRF_KEY_BYTES];
uint8_t prfout[SHARED_SECRET_BYTES];
/* Calculate approximate shared secret */
threebears_hash_init(&ctx, HASH_PURPOSE_KEYGEN);
cshake256_inc_absorb(&ctx, sk, PRIVATE_KEY_BYTES);
for (uint8_t i = 0; i < DIM; i++) {
PQCLEAN_PAPABEAR_CLEAN_expand(b, &capsule[i * GF_BYTES]);
noise(ska, &ctx, i);
PQCLEAN_PAPABEAR_CLEAN_mac(c, ska, b);
}
/* Recover seed from LPR data */
PQCLEAN_PAPABEAR_CLEAN_canon(c);
rounding = 1 << (LPR_BITS - 1);
out = 0;
for (int32_t i = ENC_BITS - 1; i >= 0; i--) {
j = (size_t) ((i & 1) ? DIGITS - i / 2 - 1 : i / 2);
our_rlimb = (limb_t)(c[j] >> (LGX - LPR_BITS - 1));
their_rlimb = (limb_t)(lpr_data[i * LPR_BITS / 8] >> ((i * LPR_BITS) % 8));
delta = (limb_t)(their_rlimb * 2 - our_rlimb + rounding);
out |= (limb_t)(((delta >> LPR_BITS) & 1) << (i % 8));
if (i % 8 == 0) {
seed[i / 8] = (uint8_t)out;
out = 0;
}
}
PQCLEAN_PAPABEAR_CLEAN_melas_fec_correct(seed, ENC_SEED_BYTES, &seed[ENC_SEED_BYTES]);
/* Re-encapsulate and check; encapsulate will compute the shared secret */
//pk[PUBLIC_KEY_BYTES], capsule2[CAPSULE_BYTES];
PQCLEAN_PAPABEAR_CLEAN_get_pubkey(pk, sk);
memcpy(&seed[ENC_SEED_BYTES], &lpr_data[(ENC_BITS * LPR_BITS + 7) / 8], IV_BYTES);
PQCLEAN_PAPABEAR_CLEAN_encapsulate(shared_secret, capsule2, pk, seed);
/* Check capsule == capsule2 in constant time */
ret = 0;
for (size_t i = 0; i < CAPSULE_BYTES; i++) {
ret |= capsule[i] ^ capsule2[i];
}
ok = (uint8_t)(((int)ret - 1) >> 8);
/* Calculate PRF key */
sep = 0xFF;
cshake256_inc_absorb(&ctx, &sep, 1);
cshake256_inc_finalize(&ctx);
cshake256_inc_squeeze(prfk, PRF_KEY_BYTES, &ctx);
/* Calculate PRF */
threebears_hash_init(&ctx, HASH_PURPOSE_PRF);
cshake256_inc_absorb(&ctx, prfk, PRF_KEY_BYTES);
cshake256_inc_absorb(&ctx, capsule, CAPSULE_BYTES);
cshake256_inc_finalize(&ctx);
cshake256_inc_squeeze(prfout, SHARED_SECRET_BYTES, &ctx);
for (size_t i = 0; i < SHARED_SECRET_BYTES; i++) {
shared_secret[i] = (uint8_t)((shared_secret[i] & ok) | (prfout[i] & ~ok));
}
}