mirror of
https://github.com/arnaucube/miden-crypto.git
synced 2026-01-10 16:11:30 +01:00
Tracking PR for v0.9.0 release (#278)
* chore: update crate version to v0.9.0 * chore: remove deprecated re-exports * chore: remove Box re-export * feat: implement pure-Rust keygen and signing for RpoFalcon512 (#285) * feat: add reproducible builds (#296) * fix: address a few issues for migrating Miden VM (#298) * feat: add RngCore supertrait for FeltRng (#299) --------- Co-authored-by: Al-Kindi-0 <82364884+Al-Kindi-0@users.noreply.github.com> Co-authored-by: Paul-Henry Kajfasz <42912740+phklive@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
2be17b74fb
commit
5a2e917dd5
@@ -1,56 +0,0 @@
|
||||
use core::fmt;
|
||||
|
||||
use super::{LOG_N, MODULUS, PK_LEN};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum FalconError {
|
||||
KeyGenerationFailed,
|
||||
PubKeyDecodingExtraData,
|
||||
PubKeyDecodingInvalidCoefficient(u32),
|
||||
PubKeyDecodingInvalidLength(usize),
|
||||
PubKeyDecodingInvalidTag(u8),
|
||||
SigDecodingTooBigHighBits(u32),
|
||||
SigDecodingInvalidRemainder,
|
||||
SigDecodingNonZeroUnusedBitsLastByte,
|
||||
SigDecodingMinusZero,
|
||||
SigDecodingIncorrectEncodingAlgorithm,
|
||||
SigDecodingNotSupportedDegree(u8),
|
||||
SigGenerationFailed,
|
||||
}
|
||||
|
||||
impl fmt::Display for FalconError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
use FalconError::*;
|
||||
match self {
|
||||
KeyGenerationFailed => write!(f, "Failed to generate a private-public key pair"),
|
||||
PubKeyDecodingExtraData => {
|
||||
write!(f, "Failed to decode public key: input not fully consumed")
|
||||
}
|
||||
PubKeyDecodingInvalidCoefficient(val) => {
|
||||
write!(f, "Failed to decode public key: coefficient {val} is greater than or equal to the field modulus {MODULUS}")
|
||||
}
|
||||
PubKeyDecodingInvalidLength(len) => {
|
||||
write!(f, "Failed to decode public key: expected {PK_LEN} bytes but received {len}")
|
||||
}
|
||||
PubKeyDecodingInvalidTag(byte) => {
|
||||
write!(f, "Failed to decode public key: expected the first byte to be {LOG_N} but was {byte}")
|
||||
}
|
||||
SigDecodingTooBigHighBits(m) => {
|
||||
write!(f, "Failed to decode signature: high bits {m} exceed 2048")
|
||||
}
|
||||
SigDecodingInvalidRemainder => {
|
||||
write!(f, "Failed to decode signature: incorrect remaining data")
|
||||
}
|
||||
SigDecodingNonZeroUnusedBitsLastByte => {
|
||||
write!(f, "Failed to decode signature: Non-zero unused bits in the last byte")
|
||||
}
|
||||
SigDecodingMinusZero => write!(f, "Failed to decode signature: -0 is forbidden"),
|
||||
SigDecodingIncorrectEncodingAlgorithm => write!(f, "Failed to decode signature: not supported encoding algorithm"),
|
||||
SigDecodingNotSupportedDegree(log_n) => write!(f, "Failed to decode signature: only supported irreducible polynomial degree is 512, 2^{log_n} was provided"),
|
||||
SigGenerationFailed => write!(f, "Failed to generate a signature"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl std::error::Error for FalconError {}
|
||||
@@ -1,402 +0,0 @@
|
||||
/*
|
||||
* Wrapper for implementing the PQClean API.
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include "randombytes.h"
|
||||
#include "falcon.h"
|
||||
#include "inner.h"
|
||||
#include "rpo.h"
|
||||
|
||||
#define NONCELEN 40
|
||||
|
||||
/*
|
||||
* Encoding formats (nnnn = log of degree, 9 for Falcon-512, 10 for Falcon-1024)
|
||||
*
|
||||
* private key:
|
||||
* header byte: 0101nnnn
|
||||
* private f (6 or 5 bits by element, depending on degree)
|
||||
* private g (6 or 5 bits by element, depending on degree)
|
||||
* private F (8 bits by element)
|
||||
*
|
||||
* public key:
|
||||
* header byte: 0000nnnn
|
||||
* public h (14 bits by element)
|
||||
*
|
||||
* signature:
|
||||
* header byte: 0011nnnn
|
||||
* nonce 40 bytes
|
||||
* value (12 bits by element)
|
||||
*
|
||||
* message + signature:
|
||||
* signature length (2 bytes, big-endian)
|
||||
* nonce 40 bytes
|
||||
* message
|
||||
* header byte: 0010nnnn
|
||||
* value (12 bits by element)
|
||||
* (signature length is 1+len(value), not counting the nonce)
|
||||
*/
|
||||
|
||||
/* see falcon.h */
|
||||
int PQCLEAN_FALCON512_CLEAN_crypto_sign_keypair_from_seed_rpo(
|
||||
uint8_t *pk,
|
||||
uint8_t *sk,
|
||||
unsigned char *seed
|
||||
) {
|
||||
union
|
||||
{
|
||||
uint8_t b[FALCON_KEYGEN_TEMP_9];
|
||||
uint64_t dummy_u64;
|
||||
fpr dummy_fpr;
|
||||
} tmp;
|
||||
int8_t f[512], g[512], F[512];
|
||||
uint16_t h[512];
|
||||
inner_shake256_context rng;
|
||||
size_t u, v;
|
||||
|
||||
/*
|
||||
* Generate key pair.
|
||||
*/
|
||||
inner_shake256_init(&rng);
|
||||
inner_shake256_inject(&rng, seed, sizeof seed);
|
||||
inner_shake256_flip(&rng);
|
||||
PQCLEAN_FALCON512_CLEAN_keygen(&rng, f, g, F, NULL, h, 9, tmp.b);
|
||||
inner_shake256_ctx_release(&rng);
|
||||
|
||||
/*
|
||||
* Encode private key.
|
||||
*/
|
||||
sk[0] = 0x50 + 9;
|
||||
u = 1;
|
||||
v = PQCLEAN_FALCON512_CLEAN_trim_i8_encode(
|
||||
sk + u, PQCLEAN_FALCON512_CLEAN_CRYPTO_SECRETKEYBYTES - u,
|
||||
f, 9, PQCLEAN_FALCON512_CLEAN_max_fg_bits[9]);
|
||||
if (v == 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
u += v;
|
||||
v = PQCLEAN_FALCON512_CLEAN_trim_i8_encode(
|
||||
sk + u, PQCLEAN_FALCON512_CLEAN_CRYPTO_SECRETKEYBYTES - u,
|
||||
g, 9, PQCLEAN_FALCON512_CLEAN_max_fg_bits[9]);
|
||||
if (v == 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
u += v;
|
||||
v = PQCLEAN_FALCON512_CLEAN_trim_i8_encode(
|
||||
sk + u, PQCLEAN_FALCON512_CLEAN_CRYPTO_SECRETKEYBYTES - u,
|
||||
F, 9, PQCLEAN_FALCON512_CLEAN_max_FG_bits[9]);
|
||||
if (v == 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
u += v;
|
||||
if (u != PQCLEAN_FALCON512_CLEAN_CRYPTO_SECRETKEYBYTES)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Encode public key.
|
||||
*/
|
||||
pk[0] = 0x00 + 9;
|
||||
v = PQCLEAN_FALCON512_CLEAN_modq_encode(
|
||||
pk + 1, PQCLEAN_FALCON512_CLEAN_CRYPTO_PUBLICKEYBYTES - 1,
|
||||
h, 9);
|
||||
if (v != PQCLEAN_FALCON512_CLEAN_CRYPTO_PUBLICKEYBYTES - 1)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int PQCLEAN_FALCON512_CLEAN_crypto_sign_keypair_rpo(
|
||||
uint8_t *pk,
|
||||
uint8_t *sk
|
||||
) {
|
||||
unsigned char seed[48];
|
||||
|
||||
/*
|
||||
* Generate a random seed.
|
||||
*/
|
||||
randombytes(seed, sizeof seed);
|
||||
|
||||
return PQCLEAN_FALCON512_CLEAN_crypto_sign_keypair_from_seed_rpo(pk, sk, seed);
|
||||
}
|
||||
|
||||
/*
|
||||
* Compute the signature. nonce[] receives the nonce and must have length
|
||||
* NONCELEN bytes. sigbuf[] receives the signature value (without nonce
|
||||
* or header byte), with *sigbuflen providing the maximum value length and
|
||||
* receiving the actual value length.
|
||||
*
|
||||
* If a signature could be computed but not encoded because it would
|
||||
* exceed the output buffer size, then a new signature is computed. If
|
||||
* the provided buffer size is too low, this could loop indefinitely, so
|
||||
* the caller must provide a size that can accommodate signatures with a
|
||||
* large enough probability.
|
||||
*
|
||||
* Return value: 0 on success, -1 on error.
|
||||
*/
|
||||
static int do_sign(
|
||||
uint8_t *nonce,
|
||||
uint8_t *sigbuf,
|
||||
size_t *sigbuflen,
|
||||
const uint8_t *m,
|
||||
size_t mlen,
|
||||
const uint8_t *sk
|
||||
) {
|
||||
union
|
||||
{
|
||||
uint8_t b[72 * 512];
|
||||
uint64_t dummy_u64;
|
||||
fpr dummy_fpr;
|
||||
} tmp;
|
||||
int8_t f[512], g[512], F[512], G[512];
|
||||
struct
|
||||
{
|
||||
int16_t sig[512];
|
||||
uint16_t hm[512];
|
||||
} r;
|
||||
unsigned char seed[48];
|
||||
inner_shake256_context sc;
|
||||
rpo128_context rc;
|
||||
size_t u, v;
|
||||
|
||||
/*
|
||||
* Decode the private key.
|
||||
*/
|
||||
if (sk[0] != 0x50 + 9)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
u = 1;
|
||||
v = PQCLEAN_FALCON512_CLEAN_trim_i8_decode(
|
||||
f, 9, PQCLEAN_FALCON512_CLEAN_max_fg_bits[9],
|
||||
sk + u, PQCLEAN_FALCON512_CLEAN_CRYPTO_SECRETKEYBYTES - u);
|
||||
if (v == 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
u += v;
|
||||
v = PQCLEAN_FALCON512_CLEAN_trim_i8_decode(
|
||||
g, 9, PQCLEAN_FALCON512_CLEAN_max_fg_bits[9],
|
||||
sk + u, PQCLEAN_FALCON512_CLEAN_CRYPTO_SECRETKEYBYTES - u);
|
||||
if (v == 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
u += v;
|
||||
v = PQCLEAN_FALCON512_CLEAN_trim_i8_decode(
|
||||
F, 9, PQCLEAN_FALCON512_CLEAN_max_FG_bits[9],
|
||||
sk + u, PQCLEAN_FALCON512_CLEAN_CRYPTO_SECRETKEYBYTES - u);
|
||||
if (v == 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
u += v;
|
||||
if (u != PQCLEAN_FALCON512_CLEAN_CRYPTO_SECRETKEYBYTES)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
if (!PQCLEAN_FALCON512_CLEAN_complete_private(G, f, g, F, 9, tmp.b))
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Create a random nonce (40 bytes).
|
||||
*/
|
||||
randombytes(nonce, NONCELEN);
|
||||
|
||||
/* ==== Start: Deviation from the reference implementation ================================= */
|
||||
|
||||
// Transform the nonce into 8 chunks each of size 5 bytes. We do this in order to be sure that
|
||||
// the conversion to field elements succeeds
|
||||
uint8_t buffer[64];
|
||||
memset(buffer, 0, 64);
|
||||
for (size_t i = 0; i < 8; i++)
|
||||
{
|
||||
buffer[8 * i] = nonce[5 * i];
|
||||
buffer[8 * i + 1] = nonce[5 * i + 1];
|
||||
buffer[8 * i + 2] = nonce[5 * i + 2];
|
||||
buffer[8 * i + 3] = nonce[5 * i + 3];
|
||||
buffer[8 * i + 4] = nonce[5 * i + 4];
|
||||
}
|
||||
|
||||
/*
|
||||
* Hash message nonce + message into a vector.
|
||||
*/
|
||||
rpo128_init(&rc);
|
||||
rpo128_absorb(&rc, buffer, NONCELEN + 24);
|
||||
rpo128_absorb(&rc, m, mlen);
|
||||
rpo128_finalize(&rc);
|
||||
PQCLEAN_FALCON512_CLEAN_hash_to_point_rpo(&rc, r.hm, 9);
|
||||
rpo128_release(&rc);
|
||||
|
||||
/* ==== End: Deviation from the reference implementation =================================== */
|
||||
|
||||
/*
|
||||
* Initialize a RNG.
|
||||
*/
|
||||
randombytes(seed, sizeof seed);
|
||||
inner_shake256_init(&sc);
|
||||
inner_shake256_inject(&sc, seed, sizeof seed);
|
||||
inner_shake256_flip(&sc);
|
||||
|
||||
/*
|
||||
* Compute and return the signature. This loops until a signature
|
||||
* value is found that fits in the provided buffer.
|
||||
*/
|
||||
for (;;)
|
||||
{
|
||||
PQCLEAN_FALCON512_CLEAN_sign_dyn(r.sig, &sc, f, g, F, G, r.hm, 9, tmp.b);
|
||||
v = PQCLEAN_FALCON512_CLEAN_comp_encode(sigbuf, *sigbuflen, r.sig, 9);
|
||||
if (v != 0)
|
||||
{
|
||||
inner_shake256_ctx_release(&sc);
|
||||
*sigbuflen = v;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Verify a signature. The nonce has size NONCELEN bytes. sigbuf[]
|
||||
* (of size sigbuflen) contains the signature value, not including the
|
||||
* header byte or nonce. Return value is 0 on success, -1 on error.
|
||||
*/
|
||||
static int do_verify(
|
||||
const uint8_t *nonce,
|
||||
const uint8_t *sigbuf,
|
||||
size_t sigbuflen,
|
||||
const uint8_t *m,
|
||||
size_t mlen,
|
||||
const uint8_t *pk
|
||||
) {
|
||||
union
|
||||
{
|
||||
uint8_t b[2 * 512];
|
||||
uint64_t dummy_u64;
|
||||
fpr dummy_fpr;
|
||||
} tmp;
|
||||
uint16_t h[512], hm[512];
|
||||
int16_t sig[512];
|
||||
rpo128_context rc;
|
||||
|
||||
/*
|
||||
* Decode public key.
|
||||
*/
|
||||
if (pk[0] != 0x00 + 9)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
if (PQCLEAN_FALCON512_CLEAN_modq_decode(h, 9,
|
||||
pk + 1, PQCLEAN_FALCON512_CLEAN_CRYPTO_PUBLICKEYBYTES - 1)
|
||||
!= PQCLEAN_FALCON512_CLEAN_CRYPTO_PUBLICKEYBYTES - 1)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
PQCLEAN_FALCON512_CLEAN_to_ntt_monty(h, 9);
|
||||
|
||||
/*
|
||||
* Decode signature.
|
||||
*/
|
||||
if (sigbuflen == 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
if (PQCLEAN_FALCON512_CLEAN_comp_decode(sig, 9, sigbuf, sigbuflen) != sigbuflen)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* ==== Start: Deviation from the reference implementation ================================= */
|
||||
|
||||
/*
|
||||
* Hash nonce + message into a vector.
|
||||
*/
|
||||
|
||||
// Transform the nonce into 8 chunks each of size 5 bytes. We do this in order to be sure that
|
||||
// the conversion to field elements succeeds
|
||||
uint8_t buffer[64];
|
||||
memset(buffer, 0, 64);
|
||||
for (size_t i = 0; i < 8; i++)
|
||||
{
|
||||
buffer[8 * i] = nonce[5 * i];
|
||||
buffer[8 * i + 1] = nonce[5 * i + 1];
|
||||
buffer[8 * i + 2] = nonce[5 * i + 2];
|
||||
buffer[8 * i + 3] = nonce[5 * i + 3];
|
||||
buffer[8 * i + 4] = nonce[5 * i + 4];
|
||||
}
|
||||
|
||||
rpo128_init(&rc);
|
||||
rpo128_absorb(&rc, buffer, NONCELEN + 24);
|
||||
rpo128_absorb(&rc, m, mlen);
|
||||
rpo128_finalize(&rc);
|
||||
PQCLEAN_FALCON512_CLEAN_hash_to_point_rpo(&rc, hm, 9);
|
||||
rpo128_release(&rc);
|
||||
|
||||
/* === End: Deviation from the reference implementation ==================================== */
|
||||
|
||||
/*
|
||||
* Verify signature.
|
||||
*/
|
||||
if (!PQCLEAN_FALCON512_CLEAN_verify_raw(hm, sig, h, 9, tmp.b))
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* see falcon.h */
|
||||
int PQCLEAN_FALCON512_CLEAN_crypto_sign_signature_rpo(
|
||||
uint8_t *sig,
|
||||
size_t *siglen,
|
||||
const uint8_t *m,
|
||||
size_t mlen,
|
||||
const uint8_t *sk
|
||||
) {
|
||||
/*
|
||||
* The PQCLEAN_FALCON512_CLEAN_CRYPTO_BYTES constant is used for
|
||||
* the signed message object (as produced by crypto_sign())
|
||||
* and includes a two-byte length value, so we take care here
|
||||
* to only generate signatures that are two bytes shorter than
|
||||
* the maximum. This is done to ensure that crypto_sign()
|
||||
* and crypto_sign_signature() produce the exact same signature
|
||||
* value, if used on the same message, with the same private key,
|
||||
* and using the same output from randombytes() (this is for
|
||||
* reproducibility of tests).
|
||||
*/
|
||||
size_t vlen;
|
||||
|
||||
vlen = PQCLEAN_FALCON512_CLEAN_CRYPTO_BYTES - NONCELEN - 3;
|
||||
if (do_sign(sig + 1, sig + 1 + NONCELEN, &vlen, m, mlen, sk) < 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
sig[0] = 0x30 + 9;
|
||||
*siglen = 1 + NONCELEN + vlen;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* see falcon.h */
|
||||
int PQCLEAN_FALCON512_CLEAN_crypto_sign_verify_rpo(
|
||||
const uint8_t *sig,
|
||||
size_t siglen,
|
||||
const uint8_t *m,
|
||||
size_t mlen,
|
||||
const uint8_t *pk
|
||||
) {
|
||||
if (siglen < 1 + NONCELEN)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
if (sig[0] != 0x30 + 9)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
return do_verify(sig + 1, sig + 1 + NONCELEN, siglen - 1 - NONCELEN, m, mlen, pk);
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#define PQCLEAN_FALCON512_CLEAN_CRYPTO_SECRETKEYBYTES 1281
|
||||
#define PQCLEAN_FALCON512_CLEAN_CRYPTO_PUBLICKEYBYTES 897
|
||||
#define PQCLEAN_FALCON512_CLEAN_CRYPTO_BYTES 666
|
||||
|
||||
/*
|
||||
* Generate a new key pair. Public key goes into pk[], private key in sk[].
|
||||
* Key sizes are exact (in bytes):
|
||||
* public (pk): PQCLEAN_FALCON512_CLEAN_CRYPTO_PUBLICKEYBYTES
|
||||
* private (sk): PQCLEAN_FALCON512_CLEAN_CRYPTO_SECRETKEYBYTES
|
||||
*
|
||||
* Return value: 0 on success, -1 on error.
|
||||
*
|
||||
* Note: This implementation follows the reference implementation in PQClean
|
||||
* https://github.com/PQClean/PQClean/tree/master/crypto_sign/falcon-512
|
||||
* verbatim except for the sections that are marked otherwise.
|
||||
*/
|
||||
int PQCLEAN_FALCON512_CLEAN_crypto_sign_keypair_rpo(
|
||||
uint8_t *pk, uint8_t *sk);
|
||||
|
||||
/*
|
||||
* Generate a new key pair from seed. Public key goes into pk[], private key in sk[].
|
||||
* Key sizes are exact (in bytes):
|
||||
* public (pk): PQCLEAN_FALCON512_CLEAN_CRYPTO_PUBLICKEYBYTES
|
||||
* private (sk): PQCLEAN_FALCON512_CLEAN_CRYPTO_SECRETKEYBYTES
|
||||
*
|
||||
* Return value: 0 on success, -1 on error.
|
||||
*/
|
||||
int PQCLEAN_FALCON512_CLEAN_crypto_sign_keypair_from_seed_rpo(
|
||||
uint8_t *pk, uint8_t *sk, unsigned char *seed);
|
||||
|
||||
/*
|
||||
* Compute a signature on a provided message (m, mlen), with a given
|
||||
* private key (sk). Signature is written in sig[], with length written
|
||||
* into *siglen. Signature length is variable; maximum signature length
|
||||
* (in bytes) is PQCLEAN_FALCON512_CLEAN_CRYPTO_BYTES.
|
||||
*
|
||||
* sig[], m[] and sk[] may overlap each other arbitrarily.
|
||||
*
|
||||
* Return value: 0 on success, -1 on error.
|
||||
*
|
||||
* Note: This implementation follows the reference implementation in PQClean
|
||||
* https://github.com/PQClean/PQClean/tree/master/crypto_sign/falcon-512
|
||||
* verbatim except for the sections that are marked otherwise.
|
||||
*/
|
||||
int PQCLEAN_FALCON512_CLEAN_crypto_sign_signature_rpo(
|
||||
uint8_t *sig, size_t *siglen,
|
||||
const uint8_t *m, size_t mlen, const uint8_t *sk);
|
||||
|
||||
/*
|
||||
* Verify a signature (sig, siglen) on a message (m, mlen) with a given
|
||||
* public key (pk).
|
||||
*
|
||||
* sig[], m[] and pk[] may overlap each other arbitrarily.
|
||||
*
|
||||
* Return value: 0 on success, -1 on error.
|
||||
*
|
||||
* Note: This implementation follows the reference implementation in PQClean
|
||||
* https://github.com/PQClean/PQClean/tree/master/crypto_sign/falcon-512
|
||||
* verbatim except for the sections that are marked otherwise.
|
||||
*/
|
||||
int PQCLEAN_FALCON512_CLEAN_crypto_sign_verify_rpo(
|
||||
const uint8_t *sig, size_t siglen,
|
||||
const uint8_t *m, size_t mlen, const uint8_t *pk);
|
||||
@@ -1,582 +0,0 @@
|
||||
/*
|
||||
* RPO implementation.
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
/* ================================================================================================
|
||||
* Modular Arithmetic
|
||||
*/
|
||||
|
||||
#define P 0xFFFFFFFF00000001
|
||||
#define M 12289
|
||||
|
||||
// From https://github.com/ncw/iprime/blob/master/mod_math_noasm.go
|
||||
static uint64_t add_mod_p(uint64_t a, uint64_t b)
|
||||
{
|
||||
a = P - a;
|
||||
uint64_t res = b - a;
|
||||
if (b < a)
|
||||
res += P;
|
||||
return res;
|
||||
}
|
||||
|
||||
static uint64_t sub_mod_p(uint64_t a, uint64_t b)
|
||||
{
|
||||
uint64_t r = a - b;
|
||||
if (a < b)
|
||||
r += P;
|
||||
return r;
|
||||
}
|
||||
|
||||
static uint64_t reduce_mod_p(uint64_t b, uint64_t a)
|
||||
{
|
||||
uint32_t d = b >> 32,
|
||||
c = b;
|
||||
if (a >= P)
|
||||
a -= P;
|
||||
a = sub_mod_p(a, c);
|
||||
a = sub_mod_p(a, d);
|
||||
a = add_mod_p(a, ((uint64_t)c) << 32);
|
||||
return a;
|
||||
}
|
||||
|
||||
static uint64_t mult_mod_p(uint64_t x, uint64_t y)
|
||||
{
|
||||
uint32_t a = x,
|
||||
b = x >> 32,
|
||||
c = y,
|
||||
d = y >> 32;
|
||||
|
||||
/* first synthesize the product using 32*32 -> 64 bit multiplies */
|
||||
x = b * (uint64_t)c; /* b*c */
|
||||
y = a * (uint64_t)d; /* a*d */
|
||||
uint64_t e = a * (uint64_t)c, /* a*c */
|
||||
f = b * (uint64_t)d, /* b*d */
|
||||
t;
|
||||
|
||||
x += y; /* b*c + a*d */
|
||||
/* carry? */
|
||||
if (x < y)
|
||||
f += 1LL << 32; /* carry into upper 32 bits - can't overflow */
|
||||
|
||||
t = x << 32;
|
||||
e += t; /* a*c + LSW(b*c + a*d) */
|
||||
/* carry? */
|
||||
if (e < t)
|
||||
f += 1; /* carry into upper 64 bits - can't overflow*/
|
||||
t = x >> 32;
|
||||
f += t; /* b*d + MSW(b*c + a*d) */
|
||||
/* can't overflow */
|
||||
|
||||
/* now reduce: (b*d + MSW(b*c + a*d), a*c + LSW(b*c + a*d)) */
|
||||
return reduce_mod_p(f, e);
|
||||
}
|
||||
|
||||
/* ================================================================================================
|
||||
* RPO128 Permutation
|
||||
*/
|
||||
|
||||
#define STATE_WIDTH 12
|
||||
#define NUM_ROUNDS 7
|
||||
|
||||
/*
|
||||
* MDS matrix
|
||||
*/
|
||||
static const uint64_t MDS[12][12] = {
|
||||
{ 7, 23, 8, 26, 13, 10, 9, 7, 6, 22, 21, 8 },
|
||||
{ 8, 7, 23, 8, 26, 13, 10, 9, 7, 6, 22, 21 },
|
||||
{ 21, 8, 7, 23, 8, 26, 13, 10, 9, 7, 6, 22 },
|
||||
{ 22, 21, 8, 7, 23, 8, 26, 13, 10, 9, 7, 6 },
|
||||
{ 6, 22, 21, 8, 7, 23, 8, 26, 13, 10, 9, 7 },
|
||||
{ 7, 6, 22, 21, 8, 7, 23, 8, 26, 13, 10, 9 },
|
||||
{ 9, 7, 6, 22, 21, 8, 7, 23, 8, 26, 13, 10 },
|
||||
{ 10, 9, 7, 6, 22, 21, 8, 7, 23, 8, 26, 13 },
|
||||
{ 13, 10, 9, 7, 6, 22, 21, 8, 7, 23, 8, 26 },
|
||||
{ 26, 13, 10, 9, 7, 6, 22, 21, 8, 7, 23, 8 },
|
||||
{ 8, 26, 13, 10, 9, 7, 6, 22, 21, 8, 7, 23 },
|
||||
{ 23, 8, 26, 13, 10, 9, 7, 6, 22, 21, 8, 7 },
|
||||
};
|
||||
|
||||
/*
|
||||
* Round constants.
|
||||
*/
|
||||
static const uint64_t ARK1[7][12] = {
|
||||
{
|
||||
5789762306288267392ULL,
|
||||
6522564764413701783ULL,
|
||||
17809893479458208203ULL,
|
||||
107145243989736508ULL,
|
||||
6388978042437517382ULL,
|
||||
15844067734406016715ULL,
|
||||
9975000513555218239ULL,
|
||||
3344984123768313364ULL,
|
||||
9959189626657347191ULL,
|
||||
12960773468763563665ULL,
|
||||
9602914297752488475ULL,
|
||||
16657542370200465908ULL,
|
||||
},
|
||||
{
|
||||
12987190162843096997ULL,
|
||||
653957632802705281ULL,
|
||||
4441654670647621225ULL,
|
||||
4038207883745915761ULL,
|
||||
5613464648874830118ULL,
|
||||
13222989726778338773ULL,
|
||||
3037761201230264149ULL,
|
||||
16683759727265180203ULL,
|
||||
8337364536491240715ULL,
|
||||
3227397518293416448ULL,
|
||||
8110510111539674682ULL,
|
||||
2872078294163232137ULL,
|
||||
},
|
||||
{
|
||||
18072785500942327487ULL,
|
||||
6200974112677013481ULL,
|
||||
17682092219085884187ULL,
|
||||
10599526828986756440ULL,
|
||||
975003873302957338ULL,
|
||||
8264241093196931281ULL,
|
||||
10065763900435475170ULL,
|
||||
2181131744534710197ULL,
|
||||
6317303992309418647ULL,
|
||||
1401440938888741532ULL,
|
||||
8884468225181997494ULL,
|
||||
13066900325715521532ULL,
|
||||
},
|
||||
{
|
||||
5674685213610121970ULL,
|
||||
5759084860419474071ULL,
|
||||
13943282657648897737ULL,
|
||||
1352748651966375394ULL,
|
||||
17110913224029905221ULL,
|
||||
1003883795902368422ULL,
|
||||
4141870621881018291ULL,
|
||||
8121410972417424656ULL,
|
||||
14300518605864919529ULL,
|
||||
13712227150607670181ULL,
|
||||
17021852944633065291ULL,
|
||||
6252096473787587650ULL,
|
||||
},
|
||||
{
|
||||
4887609836208846458ULL,
|
||||
3027115137917284492ULL,
|
||||
9595098600469470675ULL,
|
||||
10528569829048484079ULL,
|
||||
7864689113198939815ULL,
|
||||
17533723827845969040ULL,
|
||||
5781638039037710951ULL,
|
||||
17024078752430719006ULL,
|
||||
109659393484013511ULL,
|
||||
7158933660534805869ULL,
|
||||
2955076958026921730ULL,
|
||||
7433723648458773977ULL,
|
||||
},
|
||||
{
|
||||
16308865189192447297ULL,
|
||||
11977192855656444890ULL,
|
||||
12532242556065780287ULL,
|
||||
14594890931430968898ULL,
|
||||
7291784239689209784ULL,
|
||||
5514718540551361949ULL,
|
||||
10025733853830934803ULL,
|
||||
7293794580341021693ULL,
|
||||
6728552937464861756ULL,
|
||||
6332385040983343262ULL,
|
||||
13277683694236792804ULL,
|
||||
2600778905124452676ULL,
|
||||
},
|
||||
{
|
||||
7123075680859040534ULL,
|
||||
1034205548717903090ULL,
|
||||
7717824418247931797ULL,
|
||||
3019070937878604058ULL,
|
||||
11403792746066867460ULL,
|
||||
10280580802233112374ULL,
|
||||
337153209462421218ULL,
|
||||
13333398568519923717ULL,
|
||||
3596153696935337464ULL,
|
||||
8104208463525993784ULL,
|
||||
14345062289456085693ULL,
|
||||
17036731477169661256ULL,
|
||||
}};
|
||||
|
||||
const uint64_t ARK2[7][12] = {
|
||||
{
|
||||
6077062762357204287ULL,
|
||||
15277620170502011191ULL,
|
||||
5358738125714196705ULL,
|
||||
14233283787297595718ULL,
|
||||
13792579614346651365ULL,
|
||||
11614812331536767105ULL,
|
||||
14871063686742261166ULL,
|
||||
10148237148793043499ULL,
|
||||
4457428952329675767ULL,
|
||||
15590786458219172475ULL,
|
||||
10063319113072092615ULL,
|
||||
14200078843431360086ULL,
|
||||
},
|
||||
{
|
||||
6202948458916099932ULL,
|
||||
17690140365333231091ULL,
|
||||
3595001575307484651ULL,
|
||||
373995945117666487ULL,
|
||||
1235734395091296013ULL,
|
||||
14172757457833931602ULL,
|
||||
707573103686350224ULL,
|
||||
15453217512188187135ULL,
|
||||
219777875004506018ULL,
|
||||
17876696346199469008ULL,
|
||||
17731621626449383378ULL,
|
||||
2897136237748376248ULL,
|
||||
},
|
||||
{
|
||||
8023374565629191455ULL,
|
||||
15013690343205953430ULL,
|
||||
4485500052507912973ULL,
|
||||
12489737547229155153ULL,
|
||||
9500452585969030576ULL,
|
||||
2054001340201038870ULL,
|
||||
12420704059284934186ULL,
|
||||
355990932618543755ULL,
|
||||
9071225051243523860ULL,
|
||||
12766199826003448536ULL,
|
||||
9045979173463556963ULL,
|
||||
12934431667190679898ULL,
|
||||
},
|
||||
{
|
||||
18389244934624494276ULL,
|
||||
16731736864863925227ULL,
|
||||
4440209734760478192ULL,
|
||||
17208448209698888938ULL,
|
||||
8739495587021565984ULL,
|
||||
17000774922218161967ULL,
|
||||
13533282547195532087ULL,
|
||||
525402848358706231ULL,
|
||||
16987541523062161972ULL,
|
||||
5466806524462797102ULL,
|
||||
14512769585918244983ULL,
|
||||
10973956031244051118ULL,
|
||||
},
|
||||
{
|
||||
6982293561042362913ULL,
|
||||
14065426295947720331ULL,
|
||||
16451845770444974180ULL,
|
||||
7139138592091306727ULL,
|
||||
9012006439959783127ULL,
|
||||
14619614108529063361ULL,
|
||||
1394813199588124371ULL,
|
||||
4635111139507788575ULL,
|
||||
16217473952264203365ULL,
|
||||
10782018226466330683ULL,
|
||||
6844229992533662050ULL,
|
||||
7446486531695178711ULL,
|
||||
},
|
||||
{
|
||||
3736792340494631448ULL,
|
||||
577852220195055341ULL,
|
||||
6689998335515779805ULL,
|
||||
13886063479078013492ULL,
|
||||
14358505101923202168ULL,
|
||||
7744142531772274164ULL,
|
||||
16135070735728404443ULL,
|
||||
12290902521256031137ULL,
|
||||
12059913662657709804ULL,
|
||||
16456018495793751911ULL,
|
||||
4571485474751953524ULL,
|
||||
17200392109565783176ULL,
|
||||
},
|
||||
{
|
||||
17130398059294018733ULL,
|
||||
519782857322261988ULL,
|
||||
9625384390925085478ULL,
|
||||
1664893052631119222ULL,
|
||||
7629576092524553570ULL,
|
||||
3485239601103661425ULL,
|
||||
9755891797164033838ULL,
|
||||
15218148195153269027ULL,
|
||||
16460604813734957368ULL,
|
||||
9643968136937729763ULL,
|
||||
3611348709641382851ULL,
|
||||
18256379591337759196ULL,
|
||||
},
|
||||
};
|
||||
|
||||
static void apply_sbox(uint64_t *const state)
|
||||
{
|
||||
for (uint64_t i = 0; i < STATE_WIDTH; i++)
|
||||
{
|
||||
uint64_t t2 = mult_mod_p(*(state + i), *(state + i));
|
||||
uint64_t t4 = mult_mod_p(t2, t2);
|
||||
|
||||
*(state + i) = mult_mod_p(*(state + i), mult_mod_p(t2, t4));
|
||||
}
|
||||
}
|
||||
|
||||
static void apply_mds(uint64_t *state)
|
||||
{
|
||||
uint64_t res[STATE_WIDTH];
|
||||
for (uint64_t i = 0; i < STATE_WIDTH; i++)
|
||||
{
|
||||
res[i] = 0;
|
||||
}
|
||||
for (uint64_t i = 0; i < STATE_WIDTH; i++)
|
||||
{
|
||||
for (uint64_t j = 0; j < STATE_WIDTH; j++)
|
||||
{
|
||||
res[i] = add_mod_p(res[i], mult_mod_p(MDS[i][j], *(state + j)));
|
||||
}
|
||||
}
|
||||
|
||||
for (uint64_t i = 0; i < STATE_WIDTH; i++)
|
||||
{
|
||||
*(state + i) = res[i];
|
||||
}
|
||||
}
|
||||
|
||||
static void apply_constants(uint64_t *const state, const uint64_t *ark)
|
||||
{
|
||||
for (uint64_t i = 0; i < STATE_WIDTH; i++)
|
||||
{
|
||||
*(state + i) = add_mod_p(*(state + i), *(ark + i));
|
||||
}
|
||||
}
|
||||
|
||||
static void exp_acc(const uint64_t m, const uint64_t *base, const uint64_t *tail, uint64_t *const res)
|
||||
{
|
||||
for (uint64_t i = 0; i < m; i++)
|
||||
{
|
||||
for (uint64_t j = 0; j < STATE_WIDTH; j++)
|
||||
{
|
||||
if (i == 0)
|
||||
{
|
||||
*(res + j) = mult_mod_p(*(base + j), *(base + j));
|
||||
}
|
||||
else
|
||||
{
|
||||
*(res + j) = mult_mod_p(*(res + j), *(res + j));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (uint64_t i = 0; i < STATE_WIDTH; i++)
|
||||
{
|
||||
*(res + i) = mult_mod_p(*(res + i), *(tail + i));
|
||||
}
|
||||
}
|
||||
|
||||
static void apply_inv_sbox(uint64_t *const state)
|
||||
{
|
||||
uint64_t t1[STATE_WIDTH];
|
||||
for (uint64_t i = 0; i < STATE_WIDTH; i++)
|
||||
{
|
||||
t1[i] = 0;
|
||||
}
|
||||
for (uint64_t i = 0; i < STATE_WIDTH; i++)
|
||||
{
|
||||
t1[i] = mult_mod_p(*(state + i), *(state + i));
|
||||
}
|
||||
|
||||
uint64_t t2[STATE_WIDTH];
|
||||
for (uint64_t i = 0; i < STATE_WIDTH; i++)
|
||||
{
|
||||
t2[i] = 0;
|
||||
}
|
||||
for (uint64_t i = 0; i < STATE_WIDTH; i++)
|
||||
{
|
||||
t2[i] = mult_mod_p(t1[i], t1[i]);
|
||||
}
|
||||
|
||||
uint64_t t3[STATE_WIDTH];
|
||||
for (uint64_t i = 0; i < STATE_WIDTH; i++)
|
||||
{
|
||||
t3[i] = 0;
|
||||
}
|
||||
exp_acc(3, t2, t2, t3);
|
||||
|
||||
uint64_t t4[STATE_WIDTH];
|
||||
for (uint64_t i = 0; i < STATE_WIDTH; i++)
|
||||
{
|
||||
t4[i] = 0;
|
||||
}
|
||||
exp_acc(6, t3, t3, t4);
|
||||
|
||||
uint64_t tmp[STATE_WIDTH];
|
||||
for (uint64_t i = 0; i < STATE_WIDTH; i++)
|
||||
{
|
||||
tmp[i] = 0;
|
||||
}
|
||||
exp_acc(12, t4, t4, tmp);
|
||||
|
||||
uint64_t t5[STATE_WIDTH];
|
||||
for (uint64_t i = 0; i < STATE_WIDTH; i++)
|
||||
{
|
||||
t5[i] = 0;
|
||||
}
|
||||
exp_acc(6, tmp, t3, t5);
|
||||
|
||||
uint64_t t6[STATE_WIDTH];
|
||||
for (uint64_t i = 0; i < STATE_WIDTH; i++)
|
||||
{
|
||||
t6[i] = 0;
|
||||
}
|
||||
exp_acc(31, t5, t5, t6);
|
||||
|
||||
for (uint64_t i = 0; i < STATE_WIDTH; i++)
|
||||
{
|
||||
uint64_t a = mult_mod_p(mult_mod_p(t6[i], t6[i]), t5[i]);
|
||||
a = mult_mod_p(a, a);
|
||||
a = mult_mod_p(a, a);
|
||||
uint64_t b = mult_mod_p(mult_mod_p(t1[i], t2[i]), *(state + i));
|
||||
|
||||
*(state + i) = mult_mod_p(a, b);
|
||||
}
|
||||
}
|
||||
|
||||
static void apply_round(uint64_t *const state, const uint64_t round)
|
||||
{
|
||||
apply_mds(state);
|
||||
apply_constants(state, ARK1[round]);
|
||||
apply_sbox(state);
|
||||
|
||||
apply_mds(state);
|
||||
apply_constants(state, ARK2[round]);
|
||||
apply_inv_sbox(state);
|
||||
}
|
||||
|
||||
static void apply_permutation(uint64_t *state)
|
||||
{
|
||||
for (uint64_t i = 0; i < NUM_ROUNDS; i++)
|
||||
{
|
||||
apply_round(state, i);
|
||||
}
|
||||
}
|
||||
|
||||
/* ================================================================================================
|
||||
* RPO128 implementation. This is supposed to substitute SHAKE256 in the hash-to-point algorithm.
|
||||
*/
|
||||
|
||||
#include "rpo.h"
|
||||
|
||||
void rpo128_init(rpo128_context *rc)
|
||||
{
|
||||
rc->dptr = 32;
|
||||
|
||||
memset(rc->st.A, 0, sizeof rc->st.A);
|
||||
}
|
||||
|
||||
void rpo128_absorb(rpo128_context *rc, const uint8_t *in, size_t len)
|
||||
{
|
||||
size_t dptr;
|
||||
|
||||
dptr = (size_t)rc->dptr;
|
||||
while (len > 0)
|
||||
{
|
||||
size_t clen, u;
|
||||
|
||||
/* 136 * 8 = 1088 bit for the rate portion in the case of SHAKE256
|
||||
* For RPO, this is 64 * 8 = 512 bits
|
||||
* The capacity for SHAKE256 is at the end while for RPO128 it is at the beginning
|
||||
*/
|
||||
clen = 96 - dptr;
|
||||
if (clen > len)
|
||||
{
|
||||
clen = len;
|
||||
}
|
||||
|
||||
for (u = 0; u < clen; u++)
|
||||
{
|
||||
rc->st.dbuf[dptr + u] = in[u];
|
||||
}
|
||||
|
||||
dptr += clen;
|
||||
in += clen;
|
||||
len -= clen;
|
||||
if (dptr == 96)
|
||||
{
|
||||
apply_permutation(rc->st.A);
|
||||
dptr = 32;
|
||||
}
|
||||
}
|
||||
rc->dptr = dptr;
|
||||
}
|
||||
|
||||
void rpo128_finalize(rpo128_context *rc)
|
||||
{
|
||||
// Set dptr to the end of the buffer, so that first call to extract will call the permutation.
|
||||
rc->dptr = 96;
|
||||
}
|
||||
|
||||
void rpo128_squeeze(rpo128_context *rc, uint8_t *out, size_t len)
|
||||
{
|
||||
size_t dptr;
|
||||
|
||||
dptr = (size_t)rc->dptr;
|
||||
while (len > 0)
|
||||
{
|
||||
size_t clen;
|
||||
|
||||
if (dptr == 96)
|
||||
{
|
||||
apply_permutation(rc->st.A);
|
||||
dptr = 32;
|
||||
}
|
||||
clen = 96 - dptr;
|
||||
if (clen > len)
|
||||
{
|
||||
clen = len;
|
||||
}
|
||||
len -= clen;
|
||||
|
||||
memcpy(out, rc->st.dbuf + dptr, clen);
|
||||
dptr += clen;
|
||||
out += clen;
|
||||
}
|
||||
rc->dptr = dptr;
|
||||
}
|
||||
|
||||
void rpo128_release(rpo128_context *rc)
|
||||
{
|
||||
memset(rc->st.A, 0, sizeof rc->st.A);
|
||||
rc->dptr = 32;
|
||||
}
|
||||
|
||||
/* ================================================================================================
|
||||
* Hash-to-Point algorithm implementation based on RPO128
|
||||
*/
|
||||
|
||||
void PQCLEAN_FALCON512_CLEAN_hash_to_point_rpo(rpo128_context *rc, uint16_t *x, unsigned logn)
|
||||
{
|
||||
/*
|
||||
* This implementation avoids the rejection sampling step needed in the
|
||||
* per-the-spec implementation. It uses a remark in https://falcon-sign.info/falcon.pdf
|
||||
* page 31, which argues that the current variant is secure for the parameters set by NIST.
|
||||
* Avoiding the rejection-sampling step leads to an implementation that is constant-time.
|
||||
* TODO: Check that the current implementation is indeed constant-time.
|
||||
*/
|
||||
size_t n;
|
||||
|
||||
n = (size_t)1 << logn;
|
||||
while (n > 0)
|
||||
{
|
||||
uint8_t buf[8];
|
||||
uint64_t w;
|
||||
|
||||
rpo128_squeeze(rc, (void *)buf, sizeof buf);
|
||||
w = ((uint64_t)(buf[7]) << 56) |
|
||||
((uint64_t)(buf[6]) << 48) |
|
||||
((uint64_t)(buf[5]) << 40) |
|
||||
((uint64_t)(buf[4]) << 32) |
|
||||
((uint64_t)(buf[3]) << 24) |
|
||||
((uint64_t)(buf[2]) << 16) |
|
||||
((uint64_t)(buf[1]) << 8) |
|
||||
((uint64_t)(buf[0]));
|
||||
|
||||
w %= M;
|
||||
|
||||
*x++ = (uint16_t)w;
|
||||
n--;
|
||||
}
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
/* ================================================================================================
|
||||
* RPO hashing algorithm related structs and methods.
|
||||
*/
|
||||
|
||||
/*
|
||||
* RPO128 context.
|
||||
*
|
||||
* This structure is used by the hashing API. It is composed of an internal state that can be
|
||||
* viewed as either:
|
||||
* 1. 12 field elements in the Miden VM.
|
||||
* 2. 96 bytes.
|
||||
*
|
||||
* The first view is used for the internal state in the context of the RPO hashing algorithm. The
|
||||
* second view is used for the buffer used to absorb the data to be hashed.
|
||||
*
|
||||
* The pointer to the buffer is updated as the data is absorbed.
|
||||
*
|
||||
* 'rpo128_context' must be initialized with rpo128_init() before first use.
|
||||
*/
|
||||
typedef struct
|
||||
{
|
||||
union
|
||||
{
|
||||
uint64_t A[12];
|
||||
uint8_t dbuf[96];
|
||||
} st;
|
||||
uint64_t dptr;
|
||||
} rpo128_context;
|
||||
|
||||
/*
|
||||
* Initializes an RPO state
|
||||
*/
|
||||
void rpo128_init(rpo128_context *rc);
|
||||
|
||||
/*
|
||||
* Absorbs an array of bytes of length 'len' into the state.
|
||||
*/
|
||||
void rpo128_absorb(rpo128_context *rc, const uint8_t *in, size_t len);
|
||||
|
||||
/*
|
||||
* Squeezes an array of bytes of length 'len' from the state.
|
||||
*/
|
||||
void rpo128_squeeze(rpo128_context *rc, uint8_t *out, size_t len);
|
||||
|
||||
/*
|
||||
* Finalizes the state in preparation for squeezing.
|
||||
*
|
||||
* This function should be called after all the data has been absorbed.
|
||||
*
|
||||
* Note that the current implementation does not perform any sort of padding for domain separation
|
||||
* purposes. The reason being that, for our purposes, we always perform the following sequence:
|
||||
* 1. Absorb a Nonce (which is always 40 bytes packed as 8 field elements).
|
||||
* 2. Absorb the message (which is always 4 field elements).
|
||||
* 3. Call finalize.
|
||||
* 4. Squeeze the output.
|
||||
* 5. Call release.
|
||||
*/
|
||||
void rpo128_finalize(rpo128_context *rc);
|
||||
|
||||
/*
|
||||
* Releases the state.
|
||||
*
|
||||
* This function should be called after the squeeze operation is finished.
|
||||
*/
|
||||
void rpo128_release(rpo128_context *rc);
|
||||
|
||||
/* ================================================================================================
|
||||
* Hash-to-Point algorithm for signature generation and signature verification.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Hash-to-Point algorithm.
|
||||
*
|
||||
* This function generates a point in Z_q[x]/(phi) from a given message.
|
||||
*
|
||||
* It takes a finalized rpo128_context as input and it generates the coefficients of the polynomial
|
||||
* representing the point. The coefficients are stored in the array 'x'. The number of coefficients
|
||||
* is given by 'logn', which must in our case is 512.
|
||||
*/
|
||||
void PQCLEAN_FALCON512_CLEAN_hash_to_point_rpo(rpo128_context *rc, uint16_t *x, unsigned logn);
|
||||
@@ -1,194 +0,0 @@
|
||||
use core::ffi::c_int;
|
||||
|
||||
// C IMPLEMENTATION INTERFACE
|
||||
// ================================================================================================
|
||||
|
||||
#[link(name = "rpo_falcon512", kind = "static")]
|
||||
extern "C" {
|
||||
/// Generate a new key pair. Public key goes into pk[], private key in sk[].
|
||||
/// Key sizes are exact (in bytes):
|
||||
/// - public (pk): 897
|
||||
/// - private (sk): 1281
|
||||
///
|
||||
/// Return value: 0 on success, -1 on error.
|
||||
pub fn PQCLEAN_FALCON512_CLEAN_crypto_sign_keypair_rpo(pk: *mut u8, sk: *mut u8) -> c_int;
|
||||
|
||||
/// Generate a new key pair from seed. Public key goes into pk[], private key in sk[].
|
||||
/// Key sizes are exact (in bytes):
|
||||
/// - public (pk): 897
|
||||
/// - private (sk): 1281
|
||||
///
|
||||
/// Return value: 0 on success, -1 on error.
|
||||
pub fn PQCLEAN_FALCON512_CLEAN_crypto_sign_keypair_from_seed_rpo(
|
||||
pk: *mut u8,
|
||||
sk: *mut u8,
|
||||
seed: *const u8,
|
||||
) -> c_int;
|
||||
|
||||
/// Compute a signature on a provided message (m, mlen), with a given private key (sk).
|
||||
/// Signature is written in sig[], with length written into *siglen. Signature length is
|
||||
/// variable; maximum signature length (in bytes) is 666.
|
||||
///
|
||||
/// sig[], m[] and sk[] may overlap each other arbitrarily.
|
||||
///
|
||||
/// Return value: 0 on success, -1 on error.
|
||||
pub fn PQCLEAN_FALCON512_CLEAN_crypto_sign_signature_rpo(
|
||||
sig: *mut u8,
|
||||
siglen: *mut usize,
|
||||
m: *const u8,
|
||||
mlen: usize,
|
||||
sk: *const u8,
|
||||
) -> c_int;
|
||||
|
||||
// TEST HELPERS
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
/// Verify a signature (sig, siglen) on a message (m, mlen) with a given public key (pk).
|
||||
///
|
||||
/// sig[], m[] and pk[] may overlap each other arbitrarily.
|
||||
///
|
||||
/// Return value: 0 on success, -1 on error.
|
||||
#[cfg(test)]
|
||||
pub fn PQCLEAN_FALCON512_CLEAN_crypto_sign_verify_rpo(
|
||||
sig: *const u8,
|
||||
siglen: usize,
|
||||
m: *const u8,
|
||||
mlen: usize,
|
||||
pk: *const u8,
|
||||
) -> c_int;
|
||||
|
||||
/// Hash-to-Point algorithm.
|
||||
///
|
||||
/// This function generates a point in Z_q[x]/(phi) from a given message.
|
||||
///
|
||||
/// It takes a finalized rpo128_context as input and it generates the coefficients of the polynomial
|
||||
/// representing the point. The coefficients are stored in the array 'x'. The number of coefficients
|
||||
/// is given by 'logn', which must in our case is 512.
|
||||
#[cfg(test)]
|
||||
pub fn PQCLEAN_FALCON512_CLEAN_hash_to_point_rpo(
|
||||
rc: *mut Rpo128Context,
|
||||
x: *mut u16,
|
||||
logn: usize,
|
||||
);
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn rpo128_init(sc: *mut Rpo128Context);
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn rpo128_absorb(
|
||||
sc: *mut Rpo128Context,
|
||||
data: *const core::ffi::c_void,
|
||||
// TODO: When #![feature(c_size_t)] stabilizes, switch this to `core::ffi::size_t` to be
|
||||
// more accurate. Currently, however, all Rust targets as of this writing are such that
|
||||
// `core::ffi::size_t` and `usize` are the same size.
|
||||
len: usize,
|
||||
);
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn rpo128_finalize(sc: *mut Rpo128Context);
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[cfg(test)]
|
||||
pub struct Rpo128Context {
|
||||
pub content: [u64; 13usize],
|
||||
}
|
||||
|
||||
// TESTS
|
||||
// ================================================================================================
|
||||
|
||||
#[cfg(all(test, feature = "std"))]
|
||||
mod tests {
|
||||
use alloc::vec::Vec;
|
||||
use rand_utils::{rand_array, rand_value, rand_vector};
|
||||
|
||||
use super::*;
|
||||
use crate::dsa::rpo_falcon512::{NONCE_LEN, PK_LEN, SIG_LEN, SK_LEN};
|
||||
|
||||
#[test]
|
||||
fn falcon_ffi() {
|
||||
unsafe {
|
||||
//let mut rng = rand::thread_rng();
|
||||
|
||||
// --- generate a key pair from a seed ----------------------------
|
||||
|
||||
let mut pk = [0u8; PK_LEN];
|
||||
let mut sk = [0u8; SK_LEN];
|
||||
let seed: [u8; NONCE_LEN] = rand_array();
|
||||
|
||||
assert_eq!(
|
||||
0,
|
||||
PQCLEAN_FALCON512_CLEAN_crypto_sign_keypair_from_seed_rpo(
|
||||
pk.as_mut_ptr(),
|
||||
sk.as_mut_ptr(),
|
||||
seed.as_ptr()
|
||||
)
|
||||
);
|
||||
|
||||
// --- sign a message and make sure it verifies -------------------
|
||||
|
||||
let mlen: usize = rand_value::<u16>() as usize;
|
||||
let msg: Vec<u8> = rand_vector(mlen);
|
||||
let mut detached_sig = [0u8; NONCE_LEN + SIG_LEN];
|
||||
let mut siglen = 0;
|
||||
|
||||
assert_eq!(
|
||||
0,
|
||||
PQCLEAN_FALCON512_CLEAN_crypto_sign_signature_rpo(
|
||||
detached_sig.as_mut_ptr(),
|
||||
&mut siglen as *mut usize,
|
||||
msg.as_ptr(),
|
||||
msg.len(),
|
||||
sk.as_ptr()
|
||||
)
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
0,
|
||||
PQCLEAN_FALCON512_CLEAN_crypto_sign_verify_rpo(
|
||||
detached_sig.as_ptr(),
|
||||
siglen,
|
||||
msg.as_ptr(),
|
||||
msg.len(),
|
||||
pk.as_ptr()
|
||||
)
|
||||
);
|
||||
|
||||
// --- check verification of different signature ------------------
|
||||
|
||||
assert_eq!(
|
||||
-1,
|
||||
PQCLEAN_FALCON512_CLEAN_crypto_sign_verify_rpo(
|
||||
detached_sig.as_ptr(),
|
||||
siglen,
|
||||
msg.as_ptr(),
|
||||
msg.len() - 1,
|
||||
pk.as_ptr()
|
||||
)
|
||||
);
|
||||
|
||||
// --- check verification against a different pub key -------------
|
||||
|
||||
let mut pk_alt = [0u8; PK_LEN];
|
||||
let mut sk_alt = [0u8; SK_LEN];
|
||||
assert_eq!(
|
||||
0,
|
||||
PQCLEAN_FALCON512_CLEAN_crypto_sign_keypair_rpo(
|
||||
pk_alt.as_mut_ptr(),
|
||||
sk_alt.as_mut_ptr()
|
||||
)
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
-1,
|
||||
PQCLEAN_FALCON512_CLEAN_crypto_sign_verify_rpo(
|
||||
detached_sig.as_ptr(),
|
||||
siglen,
|
||||
msg.as_ptr(),
|
||||
msg.len(),
|
||||
pk_alt.as_ptr()
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
68
src/dsa/rpo_falcon512/hash_to_point.rs
Normal file
68
src/dsa/rpo_falcon512/hash_to_point.rs
Normal file
@@ -0,0 +1,68 @@
|
||||
use super::{math::FalconFelt, Nonce, Polynomial, Rpo256, Word, MODULUS, N, ZERO};
|
||||
use alloc::vec::Vec;
|
||||
use num::Zero;
|
||||
|
||||
// HASH-TO-POINT FUNCTIONS
|
||||
// ================================================================================================
|
||||
|
||||
/// Returns a polynomial in Z_p[x]/(phi) representing the hash of the provided message and
|
||||
/// nonce using RPO256.
|
||||
pub fn hash_to_point_rpo256(message: Word, nonce: &Nonce) -> Polynomial<FalconFelt> {
|
||||
let mut state = [ZERO; Rpo256::STATE_WIDTH];
|
||||
|
||||
// absorb the nonce into the state
|
||||
let nonce_elements = nonce.to_elements();
|
||||
for (&n, s) in nonce_elements.iter().zip(state[Rpo256::RATE_RANGE].iter_mut()) {
|
||||
*s = n;
|
||||
}
|
||||
Rpo256::apply_permutation(&mut state);
|
||||
|
||||
// absorb message into the state
|
||||
for (&m, s) in message.iter().zip(state[Rpo256::RATE_RANGE].iter_mut()) {
|
||||
*s = m;
|
||||
}
|
||||
|
||||
// squeeze the coefficients of the polynomial
|
||||
let mut i = 0;
|
||||
let mut res = [FalconFelt::zero(); N];
|
||||
for _ in 0..64 {
|
||||
Rpo256::apply_permutation(&mut state);
|
||||
for a in &state[Rpo256::RATE_RANGE] {
|
||||
res[i] = FalconFelt::new((a.as_int() % MODULUS as u64) as i16);
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
|
||||
Polynomial::new(res.to_vec())
|
||||
}
|
||||
|
||||
/// Returns a polynomial in Z_p[x]/(phi) representing the hash of the provided message and
|
||||
/// nonce using SHAKE256. This is the hash-to-point algorithm used in the reference implementation.
|
||||
#[allow(dead_code)]
|
||||
pub fn hash_to_point_shake256(message: &[u8], nonce: &Nonce) -> Polynomial<FalconFelt> {
|
||||
use sha3::{
|
||||
digest::{ExtendableOutput, Update, XofReader},
|
||||
Shake256,
|
||||
};
|
||||
|
||||
let mut data = vec![];
|
||||
data.extend_from_slice(nonce.as_bytes());
|
||||
data.extend_from_slice(message);
|
||||
const K: u32 = (1u32 << 16) / MODULUS as u32;
|
||||
|
||||
let mut hasher = Shake256::default();
|
||||
hasher.update(&data);
|
||||
let mut reader = hasher.finalize_xof();
|
||||
|
||||
let mut coefficients: Vec<FalconFelt> = Vec::with_capacity(N);
|
||||
while coefficients.len() != N {
|
||||
let mut randomness = [0u8; 2];
|
||||
reader.read(&mut randomness);
|
||||
let t = ((randomness[0] as u32) << 8) | (randomness[1] as u32);
|
||||
if t < K * MODULUS as u32 {
|
||||
coefficients.push(FalconFelt::new((t % MODULUS as u32) as i16));
|
||||
}
|
||||
}
|
||||
|
||||
Polynomial { coefficients }
|
||||
}
|
||||
@@ -1,235 +0,0 @@
|
||||
use super::{
|
||||
ByteReader, ByteWriter, Deserializable, DeserializationError, FalconError, Polynomial,
|
||||
PublicKeyBytes, Rpo256, SecretKeyBytes, Serializable, Signature, Word,
|
||||
};
|
||||
#[cfg(feature = "std")]
|
||||
use {
|
||||
super::{ffi, NonceBytes, NONCE_LEN, PK_LEN, SIG_LEN, SK_LEN},
|
||||
alloc::vec::Vec,
|
||||
};
|
||||
|
||||
// PUBLIC KEY
|
||||
// ================================================================================================
|
||||
|
||||
/// A public key for verifying signatures.
|
||||
///
|
||||
/// The public key is a [Word] (i.e., 4 field elements) that is the hash of the coefficients of
|
||||
/// the polynomial representing the raw bytes of the expanded public key.
|
||||
///
|
||||
/// For Falcon-512, the first byte of the expanded public key is always equal to log2(512) i.e., 9.
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct PublicKey(Word);
|
||||
|
||||
impl PublicKey {
|
||||
/// Returns a new [PublicKey] which is a commitment to the provided expanded public key.
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns an error if the decoding of the public key fails.
|
||||
pub fn new(pk: PublicKeyBytes) -> Result<Self, FalconError> {
|
||||
let h = Polynomial::from_pub_key(&pk)?;
|
||||
let pk_felts = h.to_elements();
|
||||
let pk_digest = Rpo256::hash_elements(&pk_felts).into();
|
||||
Ok(Self(pk_digest))
|
||||
}
|
||||
|
||||
/// Verifies the provided signature against provided message and this public key.
|
||||
pub fn verify(&self, message: Word, signature: &Signature) -> bool {
|
||||
signature.verify(message, self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PublicKey> for Word {
|
||||
fn from(key: PublicKey) -> Self {
|
||||
key.0
|
||||
}
|
||||
}
|
||||
|
||||
// KEY PAIR
|
||||
// ================================================================================================
|
||||
|
||||
/// A key pair (public and secret keys) for signing messages.
|
||||
///
|
||||
/// The secret key is a byte array of length [PK_LEN].
|
||||
/// The public key is a byte array of length [SK_LEN].
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct KeyPair {
|
||||
public_key: PublicKeyBytes,
|
||||
secret_key: SecretKeyBytes,
|
||||
}
|
||||
|
||||
#[allow(clippy::new_without_default)]
|
||||
impl KeyPair {
|
||||
// CONSTRUCTORS
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
/// Generates a (public_key, secret_key) key pair from OS-provided randomness.
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns an error if key generation fails.
|
||||
#[cfg(feature = "std")]
|
||||
pub fn new() -> Result<Self, FalconError> {
|
||||
let mut public_key = [0u8; PK_LEN];
|
||||
let mut secret_key = [0u8; SK_LEN];
|
||||
|
||||
let res = unsafe {
|
||||
ffi::PQCLEAN_FALCON512_CLEAN_crypto_sign_keypair_rpo(
|
||||
public_key.as_mut_ptr(),
|
||||
secret_key.as_mut_ptr(),
|
||||
)
|
||||
};
|
||||
|
||||
if res == 0 {
|
||||
Ok(Self { public_key, secret_key })
|
||||
} else {
|
||||
Err(FalconError::KeyGenerationFailed)
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates a (public_key, secret_key) key pair from the provided seed.
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns an error if key generation fails.
|
||||
#[cfg(feature = "std")]
|
||||
pub fn from_seed(seed: &NonceBytes) -> Result<Self, FalconError> {
|
||||
let mut public_key = [0u8; PK_LEN];
|
||||
let mut secret_key = [0u8; SK_LEN];
|
||||
|
||||
let res = unsafe {
|
||||
ffi::PQCLEAN_FALCON512_CLEAN_crypto_sign_keypair_from_seed_rpo(
|
||||
public_key.as_mut_ptr(),
|
||||
secret_key.as_mut_ptr(),
|
||||
seed.as_ptr(),
|
||||
)
|
||||
};
|
||||
|
||||
if res == 0 {
|
||||
Ok(Self { public_key, secret_key })
|
||||
} else {
|
||||
Err(FalconError::KeyGenerationFailed)
|
||||
}
|
||||
}
|
||||
|
||||
// PUBLIC ACCESSORS
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
/// Returns the public key corresponding to this key pair.
|
||||
pub fn public_key(&self) -> PublicKey {
|
||||
// TODO: memoize public key commitment as computing it requires quite a bit of hashing.
|
||||
// expect() is fine here because we assume that the key pair was constructed correctly.
|
||||
PublicKey::new(self.public_key).expect("invalid key pair")
|
||||
}
|
||||
|
||||
/// Returns the expanded public key corresponding to this key pair.
|
||||
pub fn expanded_public_key(&self) -> PublicKeyBytes {
|
||||
self.public_key
|
||||
}
|
||||
|
||||
// SIGNATURE GENERATION
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
/// Signs a message with a secret key and a seed.
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns an error of signature generation fails.
|
||||
#[cfg(feature = "std")]
|
||||
pub fn sign(&self, message: Word) -> Result<Signature, FalconError> {
|
||||
let msg = message.iter().flat_map(|e| e.as_int().to_le_bytes()).collect::<Vec<_>>();
|
||||
let msg_len = msg.len();
|
||||
let mut sig = [0_u8; SIG_LEN + NONCE_LEN];
|
||||
let mut sig_len: usize = 0;
|
||||
|
||||
let res = unsafe {
|
||||
ffi::PQCLEAN_FALCON512_CLEAN_crypto_sign_signature_rpo(
|
||||
sig.as_mut_ptr(),
|
||||
&mut sig_len as *mut usize,
|
||||
msg.as_ptr(),
|
||||
msg_len,
|
||||
self.secret_key.as_ptr(),
|
||||
)
|
||||
};
|
||||
|
||||
if res == 0 {
|
||||
Ok(Signature {
|
||||
sig,
|
||||
pk: self.public_key,
|
||||
pk_polynomial: Default::default(),
|
||||
sig_polynomial: Default::default(),
|
||||
})
|
||||
} else {
|
||||
Err(FalconError::SigGenerationFailed)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SERIALIZATION / DESERIALIZATION
|
||||
// ================================================================================================
|
||||
|
||||
impl Serializable for KeyPair {
|
||||
fn write_into<W: ByteWriter>(&self, target: &mut W) {
|
||||
target.write_bytes(&self.public_key);
|
||||
target.write_bytes(&self.secret_key);
|
||||
}
|
||||
}
|
||||
|
||||
impl Deserializable for KeyPair {
|
||||
fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
|
||||
let public_key: PublicKeyBytes = source.read_array()?;
|
||||
let secret_key: SecretKeyBytes = source.read_array()?;
|
||||
Ok(Self { public_key, secret_key })
|
||||
}
|
||||
}
|
||||
|
||||
// TESTS
|
||||
// ================================================================================================
|
||||
|
||||
#[cfg(all(test, feature = "std"))]
|
||||
mod tests {
|
||||
use rand_utils::{rand_array, rand_vector};
|
||||
|
||||
use super::{super::Felt, KeyPair, NonceBytes, Word};
|
||||
|
||||
#[test]
|
||||
fn test_falcon_verification() {
|
||||
// generate random keys
|
||||
let keys = KeyPair::new().unwrap();
|
||||
let pk = keys.public_key();
|
||||
|
||||
// sign a random message
|
||||
let message: Word = rand_vector::<Felt>(4).try_into().expect("Should not fail.");
|
||||
let signature = keys.sign(message);
|
||||
|
||||
// make sure the signature verifies correctly
|
||||
assert!(pk.verify(message, signature.as_ref().unwrap()));
|
||||
|
||||
// a signature should not verify against a wrong message
|
||||
let message2: Word = rand_vector::<Felt>(4).try_into().expect("Should not fail.");
|
||||
assert!(!pk.verify(message2, signature.as_ref().unwrap()));
|
||||
|
||||
// a signature should not verify against a wrong public key
|
||||
let keys2 = KeyPair::new().unwrap();
|
||||
assert!(!keys2.public_key().verify(message, signature.as_ref().unwrap()))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_falcon_verification_from_seed() {
|
||||
// generate keys from a random seed
|
||||
let seed: NonceBytes = rand_array();
|
||||
let keys = KeyPair::from_seed(&seed).unwrap();
|
||||
let pk = keys.public_key();
|
||||
|
||||
// sign a random message
|
||||
let message: Word = rand_vector::<Felt>(4).try_into().expect("Should not fail.");
|
||||
let signature = keys.sign(message);
|
||||
|
||||
// make sure the signature verifies correctly
|
||||
assert!(pk.verify(message, signature.as_ref().unwrap()));
|
||||
|
||||
// a signature should not verify against a wrong message
|
||||
let message2: Word = rand_vector::<Felt>(4).try_into().expect("Should not fail.");
|
||||
assert!(!pk.verify(message2, signature.as_ref().unwrap()));
|
||||
|
||||
// a signature should not verify against a wrong public key
|
||||
let keys2 = KeyPair::new().unwrap();
|
||||
assert!(!keys2.public_key().verify(message, signature.as_ref().unwrap()))
|
||||
}
|
||||
}
|
||||
53
src/dsa/rpo_falcon512/keys/mod.rs
Normal file
53
src/dsa/rpo_falcon512/keys/mod.rs
Normal file
@@ -0,0 +1,53 @@
|
||||
use super::{
|
||||
math::{FalconFelt, Polynomial},
|
||||
ByteReader, ByteWriter, Deserializable, DeserializationError, Felt, Serializable, Signature,
|
||||
Word,
|
||||
};
|
||||
|
||||
mod public_key;
|
||||
pub use public_key::{PubKeyPoly, PublicKey};
|
||||
|
||||
mod secret_key;
|
||||
pub use secret_key::SecretKey;
|
||||
|
||||
// TESTS
|
||||
// ================================================================================================
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{dsa::rpo_falcon512::SecretKey, Word, ONE};
|
||||
use rand::SeedableRng;
|
||||
use rand_chacha::ChaCha20Rng;
|
||||
use winter_math::FieldElement;
|
||||
use winter_utils::{Deserializable, Serializable};
|
||||
|
||||
#[test]
|
||||
fn test_falcon_verification() {
|
||||
let seed = [0_u8; 32];
|
||||
let mut rng = ChaCha20Rng::from_seed(seed);
|
||||
|
||||
// generate random keys
|
||||
let sk = SecretKey::with_rng(&mut rng);
|
||||
let pk = sk.public_key();
|
||||
|
||||
// test secret key serialization/deserialization
|
||||
let mut buffer = vec![];
|
||||
sk.write_into(&mut buffer);
|
||||
let sk = SecretKey::read_from_bytes(&buffer).unwrap();
|
||||
|
||||
// sign a random message
|
||||
let message: Word = [ONE; 4];
|
||||
let signature = sk.sign_with_rng(message, &mut rng);
|
||||
|
||||
// make sure the signature verifies correctly
|
||||
assert!(pk.verify(message, &signature));
|
||||
|
||||
// a signature should not verify against a wrong message
|
||||
let message2: Word = [ONE.double(); 4];
|
||||
assert!(!pk.verify(message2, &signature));
|
||||
|
||||
// a signature should not verify against a wrong public key
|
||||
let sk2 = SecretKey::with_rng(&mut rng);
|
||||
assert!(!sk2.public_key().verify(message, &signature))
|
||||
}
|
||||
}
|
||||
138
src/dsa/rpo_falcon512/keys/public_key.rs
Normal file
138
src/dsa/rpo_falcon512/keys/public_key.rs
Normal file
@@ -0,0 +1,138 @@
|
||||
use crate::dsa::rpo_falcon512::FALCON_ENCODING_BITS;
|
||||
|
||||
use super::{
|
||||
super::{Rpo256, LOG_N, N, PK_LEN},
|
||||
ByteReader, ByteWriter, Deserializable, DeserializationError, FalconFelt, Felt, Polynomial,
|
||||
Serializable, Signature, Word,
|
||||
};
|
||||
use alloc::string::ToString;
|
||||
use core::ops::Deref;
|
||||
use num::Zero;
|
||||
|
||||
// PUBLIC KEY
|
||||
// ================================================================================================
|
||||
|
||||
/// A public key for verifying signatures.
|
||||
///
|
||||
/// The public key is a [Word] (i.e., 4 field elements) that is the hash of the coefficients of
|
||||
/// the polynomial representing the raw bytes of the expanded public key. The hash is computed
|
||||
/// using Rpo256.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct PublicKey(Word);
|
||||
|
||||
impl PublicKey {
|
||||
/// Returns a new [PublicKey] which is a commitment to the provided expanded public key.
|
||||
pub fn new(pub_key: Word) -> Self {
|
||||
Self(pub_key)
|
||||
}
|
||||
|
||||
/// Verifies the provided signature against provided message and this public key.
|
||||
pub fn verify(&self, message: Word, signature: &Signature) -> bool {
|
||||
signature.verify(message, self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PubKeyPoly> for PublicKey {
|
||||
fn from(pk_poly: PubKeyPoly) -> Self {
|
||||
let pk_felts: Polynomial<Felt> = pk_poly.0.into();
|
||||
let pk_digest = Rpo256::hash_elements(&pk_felts.coefficients).into();
|
||||
Self(pk_digest)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PublicKey> for Word {
|
||||
fn from(key: PublicKey) -> Self {
|
||||
key.0
|
||||
}
|
||||
}
|
||||
|
||||
// PUBLIC KEY POLYNOMIAL
|
||||
// ================================================================================================
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct PubKeyPoly(pub Polynomial<FalconFelt>);
|
||||
|
||||
impl Deref for PubKeyPoly {
|
||||
type Target = Polynomial<FalconFelt>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Polynomial<FalconFelt>> for PubKeyPoly {
|
||||
fn from(pk_poly: Polynomial<FalconFelt>) -> Self {
|
||||
Self(pk_poly)
|
||||
}
|
||||
}
|
||||
|
||||
impl Serializable for &PubKeyPoly {
|
||||
fn write_into<W: ByteWriter>(&self, target: &mut W) {
|
||||
let mut buf = [0_u8; PK_LEN];
|
||||
buf[0] = LOG_N;
|
||||
|
||||
let mut acc = 0_u32;
|
||||
let mut acc_len: u32 = 0;
|
||||
|
||||
let mut input_pos = 1;
|
||||
for c in self.0.coefficients.iter() {
|
||||
let c = c.value();
|
||||
acc = (acc << FALCON_ENCODING_BITS) | c as u32;
|
||||
acc_len += FALCON_ENCODING_BITS;
|
||||
while acc_len >= 8 {
|
||||
acc_len -= 8;
|
||||
buf[input_pos] = (acc >> acc_len) as u8;
|
||||
input_pos += 1;
|
||||
}
|
||||
}
|
||||
if acc_len > 0 {
|
||||
buf[input_pos] = (acc >> (8 - acc_len)) as u8;
|
||||
}
|
||||
|
||||
target.write(buf);
|
||||
}
|
||||
}
|
||||
|
||||
impl Deserializable for PubKeyPoly {
|
||||
fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
|
||||
let buf = source.read_array::<PK_LEN>()?;
|
||||
|
||||
if buf[0] != LOG_N {
|
||||
return Err(DeserializationError::InvalidValue(format!(
|
||||
"Failed to decode public key: expected the first byte to be {LOG_N} but was {}",
|
||||
buf[0]
|
||||
)));
|
||||
}
|
||||
|
||||
let mut acc = 0_u32;
|
||||
let mut acc_len = 0;
|
||||
|
||||
let mut output = [FalconFelt::zero(); N];
|
||||
let mut output_idx = 0;
|
||||
|
||||
for &byte in buf.iter().skip(1) {
|
||||
acc = (acc << 8) | (byte as u32);
|
||||
acc_len += 8;
|
||||
|
||||
if acc_len >= FALCON_ENCODING_BITS {
|
||||
acc_len -= FALCON_ENCODING_BITS;
|
||||
let w = (acc >> acc_len) & 0x3FFF;
|
||||
let element = w.try_into().map_err(|err| {
|
||||
DeserializationError::InvalidValue(format!(
|
||||
"Failed to decode public key: {err}"
|
||||
))
|
||||
})?;
|
||||
output[output_idx] = element;
|
||||
output_idx += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (acc & ((1u32 << acc_len) - 1)) == 0 {
|
||||
Ok(Polynomial::new(output.to_vec()).into())
|
||||
} else {
|
||||
Err(DeserializationError::InvalidValue(
|
||||
"Failed to decode public key: input not fully consumed".to_string(),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
386
src/dsa/rpo_falcon512/keys/secret_key.rs
Normal file
386
src/dsa/rpo_falcon512/keys/secret_key.rs
Normal file
@@ -0,0 +1,386 @@
|
||||
use super::{
|
||||
super::{
|
||||
math::{ffldl, ffsampling, gram, normalize_tree, FalconFelt, FastFft, LdlTree, Polynomial},
|
||||
signature::SignaturePoly,
|
||||
ByteReader, ByteWriter, Deserializable, DeserializationError, Nonce, Serializable,
|
||||
ShortLatticeBasis, Signature, Word, MODULUS, N, SIGMA, SIG_L2_BOUND,
|
||||
},
|
||||
PubKeyPoly, PublicKey,
|
||||
};
|
||||
use crate::dsa::rpo_falcon512::{
|
||||
hash_to_point::hash_to_point_rpo256, math::ntru_gen, SIG_NONCE_LEN, SK_LEN,
|
||||
};
|
||||
use alloc::{string::ToString, vec::Vec};
|
||||
use num::Complex;
|
||||
use num_complex::Complex64;
|
||||
use rand::Rng;
|
||||
|
||||
#[cfg(not(feature = "std"))]
|
||||
use num::Float;
|
||||
|
||||
// CONSTANTS
|
||||
// ================================================================================================
|
||||
|
||||
const WIDTH_BIG_POLY_COEFFICIENT: usize = 8;
|
||||
const WIDTH_SMALL_POLY_COEFFICIENT: usize = 6;
|
||||
|
||||
// SECRET KEY
|
||||
// ================================================================================================
|
||||
|
||||
/// The secret key is a quadruple [[g, -f], [G, -F]] of polynomials with integer coefficients. Each
|
||||
/// polynomial is of degree at most N = 512 and computations with these polynomials is done modulo
|
||||
/// the monic irreducible polynomial ϕ = x^N + 1. The secret key is a basis for a lattice and has
|
||||
/// the property of being short with respect to a certain norm and an upper bound appropriate for
|
||||
/// a given security parameter. The public key on the other hand is another basis for the same
|
||||
/// lattice and can be described by a single polynomial h with integer coefficients modulo ϕ.
|
||||
/// The two keys are related by the following relation:
|
||||
///
|
||||
/// 1. h = g /f [mod ϕ][mod p]
|
||||
/// 2. f.G - g.F = p [mod ϕ]
|
||||
///
|
||||
/// where p = 12289 is the Falcon prime. Equation 2 is called the NTRU equation.
|
||||
/// The secret key is generated by first sampling a random pair (f, g) of polynomials using
|
||||
/// an appropriate distribution that yields short but not too short polynomials with integer
|
||||
/// coefficients modulo ϕ. The NTRU equation is then used to find a matching pair (F, G).
|
||||
/// The public key is then derived from the secret key using equation 1.
|
||||
///
|
||||
/// To allow for fast signature generation, the secret key is pre-processed into a more suitable
|
||||
/// form, called the LDL tree, and this allows for fast sampling of short vectors in the lattice
|
||||
/// using Fast Fourier sampling during signature generation (ffSampling algorithm 11 in [1]).
|
||||
///
|
||||
/// [1]: https://falcon-sign.info/falcon.pdf
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SecretKey {
|
||||
secret_key: ShortLatticeBasis,
|
||||
tree: LdlTree,
|
||||
}
|
||||
|
||||
#[allow(clippy::new_without_default)]
|
||||
impl SecretKey {
|
||||
// CONSTRUCTORS
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
/// Generates a secret key from OS-provided randomness.
|
||||
#[cfg(feature = "std")]
|
||||
pub fn new() -> Self {
|
||||
use rand::{rngs::StdRng, SeedableRng};
|
||||
|
||||
let mut rng = StdRng::from_entropy();
|
||||
Self::with_rng(&mut rng)
|
||||
}
|
||||
|
||||
/// Generates a secret_key using the provided random number generator `Rng`.
|
||||
pub fn with_rng<R: Rng>(rng: &mut R) -> Self {
|
||||
let basis = ntru_gen(N, rng);
|
||||
Self::from_short_lattice_basis(basis)
|
||||
}
|
||||
|
||||
/// Given a short basis [[g, -f], [G, -F]], computes the normalized LDL tree i.e., Falcon tree.
|
||||
fn from_short_lattice_basis(basis: ShortLatticeBasis) -> SecretKey {
|
||||
// FFT each polynomial of the short basis.
|
||||
let basis_fft = to_complex_fft(&basis);
|
||||
// compute the Gram matrix.
|
||||
let gram_fft = gram(basis_fft);
|
||||
// construct the LDL tree of the Gram matrix.
|
||||
let mut tree = ffldl(gram_fft);
|
||||
// normalize the leaves of the LDL tree.
|
||||
normalize_tree(&mut tree, SIGMA);
|
||||
Self { secret_key: basis, tree }
|
||||
}
|
||||
|
||||
// PUBLIC ACCESSORS
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
/// Returns the polynomials of the short lattice basis of this secret key.
|
||||
pub fn short_lattice_basis(&self) -> &ShortLatticeBasis {
|
||||
&self.secret_key
|
||||
}
|
||||
|
||||
/// Returns the public key corresponding to this secret key.
|
||||
pub fn public_key(&self) -> PublicKey {
|
||||
self.compute_pub_key_poly().into()
|
||||
}
|
||||
|
||||
/// Returns the LDL tree associated to this secret key.
|
||||
pub fn tree(&self) -> &LdlTree {
|
||||
&self.tree
|
||||
}
|
||||
|
||||
// SIGNATURE GENERATION
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
/// Signs a message with this secret key.
|
||||
#[cfg(feature = "std")]
|
||||
pub fn sign(&self, message: Word) -> Signature {
|
||||
use rand::{rngs::StdRng, SeedableRng};
|
||||
|
||||
let mut rng = StdRng::from_entropy();
|
||||
self.sign_with_rng(message, &mut rng)
|
||||
}
|
||||
|
||||
/// Signs a message with the secret key relying on the provided randomness generator.
|
||||
pub fn sign_with_rng<R: Rng>(&self, message: Word, rng: &mut R) -> Signature {
|
||||
let mut nonce_bytes = [0u8; SIG_NONCE_LEN];
|
||||
rng.fill_bytes(&mut nonce_bytes);
|
||||
let nonce = Nonce::new(nonce_bytes);
|
||||
|
||||
let h = self.compute_pub_key_poly();
|
||||
let c = hash_to_point_rpo256(message, &nonce);
|
||||
let s2 = self.sign_helper(c, rng);
|
||||
|
||||
Signature::new(nonce, h, s2)
|
||||
}
|
||||
|
||||
// HELPER METHODS
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
/// Derives the public key corresponding to this secret key using h = g /f [mod ϕ][mod p].
|
||||
pub fn compute_pub_key_poly(&self) -> PubKeyPoly {
|
||||
let g: Polynomial<FalconFelt> = self.secret_key[0].clone().into();
|
||||
let g_fft = g.fft();
|
||||
let minus_f: Polynomial<FalconFelt> = self.secret_key[1].clone().into();
|
||||
let f = -minus_f;
|
||||
let f_fft = f.fft();
|
||||
let h_fft = g_fft.hadamard_div(&f_fft);
|
||||
h_fft.ifft().into()
|
||||
}
|
||||
|
||||
/// Signs a message polynomial with the secret key.
|
||||
///
|
||||
/// Takes a randomness generator implementing `Rng` and message polynomial representing `c`
|
||||
/// the hash-to-point of the message to be signed. It outputs a signature polynomial `s2`.
|
||||
fn sign_helper<R: Rng>(&self, c: Polynomial<FalconFelt>, rng: &mut R) -> SignaturePoly {
|
||||
let one_over_q = 1.0 / (MODULUS as f64);
|
||||
let c_over_q_fft = c.map(|cc| Complex::new(one_over_q * cc.value() as f64, 0.0)).fft();
|
||||
|
||||
// B = [[FFT(g), -FFT(f)], [FFT(G), -FFT(F)]]
|
||||
let [g_fft, minus_f_fft, big_g_fft, minus_big_f_fft] = to_complex_fft(&self.secret_key);
|
||||
let t0 = c_over_q_fft.hadamard_mul(&minus_big_f_fft);
|
||||
let t1 = -c_over_q_fft.hadamard_mul(&minus_f_fft);
|
||||
|
||||
loop {
|
||||
let bold_s = loop {
|
||||
let z = ffsampling(&(t0.clone(), t1.clone()), &self.tree, rng);
|
||||
let t0_min_z0 = t0.clone() - z.0;
|
||||
let t1_min_z1 = t1.clone() - z.1;
|
||||
|
||||
// s = (t-z) * B
|
||||
let s0 = t0_min_z0.hadamard_mul(&g_fft) + t1_min_z1.hadamard_mul(&big_g_fft);
|
||||
let s1 =
|
||||
t0_min_z0.hadamard_mul(&minus_f_fft) + t1_min_z1.hadamard_mul(&minus_big_f_fft);
|
||||
|
||||
// compute the norm of (s0||s1) and note that they are in FFT representation
|
||||
let length_squared: f64 =
|
||||
(s0.coefficients.iter().map(|a| (a * a.conj()).re).sum::<f64>()
|
||||
+ s1.coefficients.iter().map(|a| (a * a.conj()).re).sum::<f64>())
|
||||
/ (N as f64);
|
||||
|
||||
if length_squared > (SIG_L2_BOUND as f64) {
|
||||
continue;
|
||||
}
|
||||
|
||||
break [-s0, s1];
|
||||
};
|
||||
|
||||
let s2 = bold_s[1].ifft();
|
||||
let s2_coef: [i16; N] = s2
|
||||
.coefficients
|
||||
.iter()
|
||||
.map(|a| a.re.round() as i16)
|
||||
.collect::<Vec<i16>>()
|
||||
.try_into()
|
||||
.expect("The number of coefficients should be equal to N");
|
||||
|
||||
if let Ok(s2) = SignaturePoly::try_from(&s2_coef) {
|
||||
return s2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SERIALIZATION / DESERIALIZATION
|
||||
// ================================================================================================
|
||||
|
||||
impl Serializable for SecretKey {
|
||||
fn write_into<W: ByteWriter>(&self, target: &mut W) {
|
||||
let basis = &self.secret_key;
|
||||
|
||||
// header
|
||||
let n = basis[0].coefficients.len();
|
||||
let l = n.checked_ilog2().unwrap() as u8;
|
||||
let header: u8 = (5 << 4) | l;
|
||||
|
||||
let f = &basis[1];
|
||||
let g = &basis[0];
|
||||
let capital_f = &basis[3];
|
||||
|
||||
let mut buffer = Vec::with_capacity(1281);
|
||||
buffer.push(header);
|
||||
|
||||
let f_i8: Vec<i8> = f.coefficients.iter().map(|&a| -a as i8).collect();
|
||||
let f_i8_encoded = encode_i8(&f_i8, WIDTH_SMALL_POLY_COEFFICIENT).unwrap();
|
||||
buffer.extend_from_slice(&f_i8_encoded);
|
||||
|
||||
let g_i8: Vec<i8> = g.coefficients.iter().map(|&a| a as i8).collect();
|
||||
let g_i8_encoded = encode_i8(&g_i8, WIDTH_SMALL_POLY_COEFFICIENT).unwrap();
|
||||
buffer.extend_from_slice(&g_i8_encoded);
|
||||
|
||||
let big_f_i8: Vec<i8> = capital_f.coefficients.iter().map(|&a| -a as i8).collect();
|
||||
let big_f_i8_encoded = encode_i8(&big_f_i8, WIDTH_BIG_POLY_COEFFICIENT).unwrap();
|
||||
buffer.extend_from_slice(&big_f_i8_encoded);
|
||||
target.write_bytes(&buffer);
|
||||
}
|
||||
}
|
||||
|
||||
impl Deserializable for SecretKey {
|
||||
fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
|
||||
let byte_vector: [u8; SK_LEN] = source.read_array()?;
|
||||
|
||||
// check length
|
||||
if byte_vector.len() < 2 {
|
||||
return Err(DeserializationError::InvalidValue("Invalid encoding length: Failed to decode as length is different from the one expected".to_string()));
|
||||
}
|
||||
|
||||
// read fields
|
||||
let header = byte_vector[0];
|
||||
|
||||
// check fixed bits in header
|
||||
if (header >> 4) != 5 {
|
||||
return Err(DeserializationError::InvalidValue("Invalid header format".to_string()));
|
||||
}
|
||||
|
||||
// check log n
|
||||
let logn = (header & 15) as usize;
|
||||
let n = 1 << logn;
|
||||
|
||||
// match against const variant generic parameter
|
||||
if n != N {
|
||||
return Err(DeserializationError::InvalidValue(
|
||||
"Unsupported Falcon DSA variant".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
if byte_vector.len() != SK_LEN {
|
||||
return Err(DeserializationError::InvalidValue("Invalid encoding length: Failed to decode as length is different from the one expected".to_string()));
|
||||
}
|
||||
|
||||
let chunk_size_f = ((n * WIDTH_SMALL_POLY_COEFFICIENT) + 7) >> 3;
|
||||
let chunk_size_g = ((n * WIDTH_SMALL_POLY_COEFFICIENT) + 7) >> 3;
|
||||
let chunk_size_big_f = ((n * WIDTH_BIG_POLY_COEFFICIENT) + 7) >> 3;
|
||||
|
||||
let f = decode_i8(&byte_vector[1..chunk_size_f + 1], WIDTH_SMALL_POLY_COEFFICIENT).unwrap();
|
||||
let g = decode_i8(
|
||||
&byte_vector[chunk_size_f + 1..(chunk_size_f + chunk_size_g + 1)],
|
||||
WIDTH_SMALL_POLY_COEFFICIENT,
|
||||
)
|
||||
.unwrap();
|
||||
let big_f = decode_i8(
|
||||
&byte_vector[(chunk_size_f + chunk_size_g + 1)
|
||||
..(chunk_size_f + chunk_size_g + chunk_size_big_f + 1)],
|
||||
WIDTH_BIG_POLY_COEFFICIENT,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let f = Polynomial::new(f.iter().map(|&c| FalconFelt::new(c.into())).collect());
|
||||
let g = Polynomial::new(g.iter().map(|&c| FalconFelt::new(c.into())).collect());
|
||||
let big_f = Polynomial::new(big_f.iter().map(|&c| FalconFelt::new(c.into())).collect());
|
||||
|
||||
// big_g * f - g * big_f = p (mod X^n + 1)
|
||||
let big_g = g.fft().hadamard_div(&f.fft()).hadamard_mul(&big_f.fft()).ifft();
|
||||
let basis = [
|
||||
g.map(|f| f.balanced_value()),
|
||||
-f.map(|f| f.balanced_value()),
|
||||
big_g.map(|f| f.balanced_value()),
|
||||
-big_f.map(|f| f.balanced_value()),
|
||||
];
|
||||
Ok(Self::from_short_lattice_basis(basis))
|
||||
}
|
||||
}
|
||||
|
||||
// HELPER FUNCTIONS
|
||||
// ================================================================================================
|
||||
|
||||
/// Computes the complex FFT of the secret key polynomials.
|
||||
fn to_complex_fft(basis: &[Polynomial<i16>; 4]) -> [Polynomial<Complex<f64>>; 4] {
|
||||
let [g, f, big_g, big_f] = basis.clone();
|
||||
let g_fft = g.map(|cc| Complex64::new(*cc as f64, 0.0)).fft();
|
||||
let minus_f_fft = f.map(|cc| -Complex64::new(*cc as f64, 0.0)).fft();
|
||||
let big_g_fft = big_g.map(|cc| Complex64::new(*cc as f64, 0.0)).fft();
|
||||
let minus_big_f_fft = big_f.map(|cc| -Complex64::new(*cc as f64, 0.0)).fft();
|
||||
[g_fft, minus_f_fft, big_g_fft, minus_big_f_fft]
|
||||
}
|
||||
|
||||
/// Encodes a sequence of signed integers such that each integer x satisfies |x| < 2^(bits-1)
|
||||
/// for a given parameter bits. bits can take either the value 6 or 8.
|
||||
pub fn encode_i8(x: &[i8], bits: usize) -> Option<Vec<u8>> {
|
||||
let maxv = (1 << (bits - 1)) - 1_usize;
|
||||
let maxv = maxv as i8;
|
||||
let minv = -maxv;
|
||||
|
||||
for &c in x {
|
||||
if c > maxv || c < minv {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
let out_len = ((N * bits) + 7) >> 3;
|
||||
let mut buf = vec![0_u8; out_len];
|
||||
|
||||
let mut acc = 0_u32;
|
||||
let mut acc_len = 0;
|
||||
let mask = ((1_u16 << bits) - 1) as u8;
|
||||
|
||||
let mut input_pos = 0;
|
||||
for &c in x {
|
||||
acc = (acc << bits) | (c as u8 & mask) as u32;
|
||||
acc_len += bits;
|
||||
while acc_len >= 8 {
|
||||
acc_len -= 8;
|
||||
buf[input_pos] = (acc >> acc_len) as u8;
|
||||
input_pos += 1;
|
||||
}
|
||||
}
|
||||
if acc_len > 0 {
|
||||
buf[input_pos] = (acc >> (8 - acc_len)) as u8;
|
||||
}
|
||||
|
||||
Some(buf)
|
||||
}
|
||||
|
||||
/// Decodes a sequence of bytes into a sequence of signed integers such that each integer x
|
||||
/// satisfies |x| < 2^(bits-1) for a given parameter bits. bits can take either the value 6 or 8.
|
||||
pub fn decode_i8(buf: &[u8], bits: usize) -> Option<Vec<i8>> {
|
||||
let mut x = [0_i8; N];
|
||||
|
||||
let mut i = 0;
|
||||
let mut j = 0;
|
||||
let mut acc = 0_u32;
|
||||
let mut acc_len = 0;
|
||||
let mask = (1_u32 << bits) - 1;
|
||||
let a = (1 << bits) as u8;
|
||||
let b = ((1 << (bits - 1)) - 1) as u8;
|
||||
|
||||
while i < N {
|
||||
acc = (acc << 8) | (buf[j] as u32);
|
||||
j += 1;
|
||||
acc_len += 8;
|
||||
|
||||
while acc_len >= bits && i < N {
|
||||
acc_len -= bits;
|
||||
let w = (acc >> acc_len) & mask;
|
||||
|
||||
let w = w as u8;
|
||||
|
||||
let z = if w > b { w as i8 - a as i8 } else { w as i8 };
|
||||
|
||||
x[i] = z;
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (acc & ((1u32 << acc_len) - 1)) == 0 {
|
||||
Some(x.to_vec())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
123
src/dsa/rpo_falcon512/math/ffsampling.rs
Normal file
123
src/dsa/rpo_falcon512/math/ffsampling.rs
Normal file
@@ -0,0 +1,123 @@
|
||||
use super::{fft::FastFft, polynomial::Polynomial, samplerz::sampler_z};
|
||||
use alloc::boxed::Box;
|
||||
use num::{One, Zero};
|
||||
use num_complex::{Complex, Complex64};
|
||||
use rand::Rng;
|
||||
|
||||
#[cfg(not(feature = "std"))]
|
||||
use num::Float;
|
||||
|
||||
const SIGMIN: f64 = 1.2778336969128337;
|
||||
|
||||
/// Computes the Gram matrix. The argument must be a 2x2 matrix
|
||||
/// whose elements are equal-length vectors of complex numbers,
|
||||
/// representing polynomials in FFT domain.
|
||||
pub fn gram(b: [Polynomial<Complex64>; 4]) -> [Polynomial<Complex64>; 4] {
|
||||
const N: usize = 2;
|
||||
let mut g: [Polynomial<Complex<f64>>; 4] =
|
||||
[Polynomial::zero(), Polynomial::zero(), Polynomial::zero(), Polynomial::zero()];
|
||||
for i in 0..N {
|
||||
for j in 0..N {
|
||||
for k in 0..N {
|
||||
g[N * i + j] = g[N * i + j].clone()
|
||||
+ b[N * i + k].hadamard_mul(&b[N * j + k].map(|c| c.conj()));
|
||||
}
|
||||
}
|
||||
}
|
||||
g
|
||||
}
|
||||
|
||||
/// Computes the LDL decomposition of a 2x2 matrix G such that
|
||||
/// L D L* = G
|
||||
/// where D is diagonal, and L is lower-triangular. The elements of the matrices are in FFT domain.
|
||||
pub fn ldl(
|
||||
g: [Polynomial<Complex64>; 4],
|
||||
) -> ([Polynomial<Complex64>; 4], [Polynomial<Complex64>; 4]) {
|
||||
let zero = Polynomial::<Complex64>::one();
|
||||
let one = Polynomial::<Complex64>::zero();
|
||||
|
||||
let l10 = g[2].hadamard_div(&g[0]);
|
||||
let bc = l10.map(|c| c * c.conj());
|
||||
let abc = g[0].hadamard_mul(&bc);
|
||||
let d11 = g[3].clone() - abc;
|
||||
|
||||
let l = [one.clone(), zero.clone(), l10.clone(), one];
|
||||
let d = [g[0].clone(), zero.clone(), zero, d11];
|
||||
(l, d)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum LdlTree {
|
||||
Branch(Polynomial<Complex64>, Box<LdlTree>, Box<LdlTree>),
|
||||
Leaf([Complex64; 2]),
|
||||
}
|
||||
|
||||
/// Computes the LDL Tree of G. Corresponds to Algorithm 9 of the specification [1, p.37].
|
||||
/// The argument is a 2x2 matrix of polynomials, given in FFT form.
|
||||
/// [1]: https://falcon-sign.info/falcon.pdf
|
||||
pub fn ffldl(gram_matrix: [Polynomial<Complex64>; 4]) -> LdlTree {
|
||||
let n = gram_matrix[0].coefficients.len();
|
||||
let (l, d) = ldl(gram_matrix);
|
||||
|
||||
if n > 2 {
|
||||
let (d00, d01) = d[0].split_fft();
|
||||
let (d10, d11) = d[3].split_fft();
|
||||
let g0 = [d00.clone(), d01.clone(), d01.map(|c| c.conj()), d00];
|
||||
let g1 = [d10.clone(), d11.clone(), d11.map(|c| c.conj()), d10];
|
||||
LdlTree::Branch(l[2].clone(), Box::new(ffldl(g0)), Box::new(ffldl(g1)))
|
||||
} else {
|
||||
LdlTree::Branch(
|
||||
l[2].clone(),
|
||||
Box::new(LdlTree::Leaf(d[0].clone().coefficients.try_into().unwrap())),
|
||||
Box::new(LdlTree::Leaf(d[3].clone().coefficients.try_into().unwrap())),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Normalizes the leaves of an LDL tree using a given normalization value `sigma`.
|
||||
pub fn normalize_tree(tree: &mut LdlTree, sigma: f64) {
|
||||
match tree {
|
||||
LdlTree::Branch(_ell, left, right) => {
|
||||
normalize_tree(left, sigma);
|
||||
normalize_tree(right, sigma);
|
||||
}
|
||||
LdlTree::Leaf(vector) => {
|
||||
vector[0] = Complex::new(sigma / vector[0].re.sqrt(), 0.0);
|
||||
vector[1] = Complex64::zero();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Samples short polynomials using a Falcon tree. Algorithm 11 from the spec [1, p.40].
|
||||
///
|
||||
/// [1]: https://falcon-sign.info/falcon.pdf
|
||||
pub fn ffsampling<R: Rng>(
|
||||
t: &(Polynomial<Complex64>, Polynomial<Complex64>),
|
||||
tree: &LdlTree,
|
||||
mut rng: &mut R,
|
||||
) -> (Polynomial<Complex64>, Polynomial<Complex64>) {
|
||||
match tree {
|
||||
LdlTree::Branch(ell, left, right) => {
|
||||
let bold_t1 = t.1.split_fft();
|
||||
let bold_z1 = ffsampling(&bold_t1, right, rng);
|
||||
let z1 = Polynomial::<Complex64>::merge_fft(&bold_z1.0, &bold_z1.1);
|
||||
|
||||
// t0' = t0 + (t1 - z1) * l
|
||||
let t0_prime = t.0.clone() + (t.1.clone() - z1.clone()).hadamard_mul(ell);
|
||||
|
||||
let bold_t0 = t0_prime.split_fft();
|
||||
let bold_z0 = ffsampling(&bold_t0, left, rng);
|
||||
let z0 = Polynomial::<Complex64>::merge_fft(&bold_z0.0, &bold_z0.1);
|
||||
|
||||
(z0, z1)
|
||||
}
|
||||
LdlTree::Leaf(value) => {
|
||||
let z0 = sampler_z(t.0.coefficients[0].re, value[0].re, SIGMIN, &mut rng);
|
||||
let z1 = sampler_z(t.1.coefficients[0].re, value[0].re, SIGMIN, &mut rng);
|
||||
(
|
||||
Polynomial::new(vec![Complex64::new(z0 as f64, 0.0)]),
|
||||
Polynomial::new(vec![Complex64::new(z1 as f64, 0.0)]),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
1927
src/dsa/rpo_falcon512/math/fft.rs
Normal file
1927
src/dsa/rpo_falcon512/math/fft.rs
Normal file
File diff suppressed because it is too large
Load Diff
172
src/dsa/rpo_falcon512/math/field.rs
Normal file
172
src/dsa/rpo_falcon512/math/field.rs
Normal file
@@ -0,0 +1,172 @@
|
||||
use super::{fft::CyclotomicFourier, Inverse, MODULUS};
|
||||
use alloc::string::String;
|
||||
use core::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign};
|
||||
use num::{One, Zero};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||
pub struct FalconFelt(u32);
|
||||
|
||||
impl FalconFelt {
|
||||
pub const fn new(value: i16) -> Self {
|
||||
let gtz_bool = value >= 0;
|
||||
let gtz_int = gtz_bool as i16;
|
||||
let gtz_sign = gtz_int - ((!gtz_bool) as i16);
|
||||
let reduced = gtz_sign * (gtz_sign * value) % MODULUS;
|
||||
let canonical_representative = (reduced + MODULUS * (1 - gtz_int)) as u32;
|
||||
FalconFelt(canonical_representative)
|
||||
}
|
||||
|
||||
pub const fn value(&self) -> i16 {
|
||||
self.0 as i16
|
||||
}
|
||||
|
||||
pub fn balanced_value(&self) -> i16 {
|
||||
let value = self.value();
|
||||
let g = (value > ((MODULUS) / 2)) as i16;
|
||||
value - (MODULUS) * g
|
||||
}
|
||||
|
||||
pub const fn multiply(&self, other: Self) -> Self {
|
||||
FalconFelt((self.0 * other.0) % MODULUS as u32)
|
||||
}
|
||||
}
|
||||
|
||||
impl Add for FalconFelt {
|
||||
type Output = Self;
|
||||
|
||||
#[allow(clippy::suspicious_arithmetic_impl)]
|
||||
fn add(self, rhs: Self) -> Self::Output {
|
||||
let (s, _) = self.0.overflowing_add(rhs.0);
|
||||
let (d, n) = s.overflowing_sub(MODULUS as u32);
|
||||
let (r, _) = d.overflowing_add(MODULUS as u32 * (n as u32));
|
||||
FalconFelt(r)
|
||||
}
|
||||
}
|
||||
|
||||
impl AddAssign for FalconFelt {
|
||||
fn add_assign(&mut self, rhs: Self) {
|
||||
*self = *self + rhs;
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub for FalconFelt {
|
||||
type Output = Self;
|
||||
|
||||
fn sub(self, rhs: Self) -> Self::Output {
|
||||
self + -rhs
|
||||
}
|
||||
}
|
||||
|
||||
impl SubAssign for FalconFelt {
|
||||
fn sub_assign(&mut self, rhs: Self) {
|
||||
*self = *self - rhs;
|
||||
}
|
||||
}
|
||||
|
||||
impl Neg for FalconFelt {
|
||||
type Output = FalconFelt;
|
||||
|
||||
fn neg(self) -> Self::Output {
|
||||
let is_nonzero = self.0 != 0;
|
||||
let r = MODULUS as u32 - self.0;
|
||||
FalconFelt(r * (is_nonzero as u32))
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul for FalconFelt {
|
||||
fn mul(self, rhs: Self) -> Self::Output {
|
||||
FalconFelt((self.0 * rhs.0) % MODULUS as u32)
|
||||
}
|
||||
|
||||
type Output = Self;
|
||||
}
|
||||
|
||||
impl MulAssign for FalconFelt {
|
||||
fn mul_assign(&mut self, rhs: Self) {
|
||||
*self = *self * rhs;
|
||||
}
|
||||
}
|
||||
|
||||
impl Div for FalconFelt {
|
||||
type Output = FalconFelt;
|
||||
|
||||
#[allow(clippy::suspicious_arithmetic_impl)]
|
||||
fn div(self, rhs: Self) -> Self::Output {
|
||||
self * rhs.inverse_or_zero()
|
||||
}
|
||||
}
|
||||
|
||||
impl DivAssign for FalconFelt {
|
||||
fn div_assign(&mut self, rhs: Self) {
|
||||
*self = *self / rhs
|
||||
}
|
||||
}
|
||||
|
||||
impl Zero for FalconFelt {
|
||||
fn zero() -> Self {
|
||||
FalconFelt::new(0)
|
||||
}
|
||||
|
||||
fn is_zero(&self) -> bool {
|
||||
self.0 == 0
|
||||
}
|
||||
}
|
||||
|
||||
impl One for FalconFelt {
|
||||
fn one() -> Self {
|
||||
FalconFelt::new(1)
|
||||
}
|
||||
}
|
||||
|
||||
impl Inverse for FalconFelt {
|
||||
fn inverse_or_zero(self) -> Self {
|
||||
// q-2 = 0b10 11 11 11 11 11 11
|
||||
let two = self.multiply(self);
|
||||
let three = two.multiply(self);
|
||||
let six = three.multiply(three);
|
||||
let twelve = six.multiply(six);
|
||||
let fifteen = twelve.multiply(three);
|
||||
let thirty = fifteen.multiply(fifteen);
|
||||
let sixty = thirty.multiply(thirty);
|
||||
let sixty_three = sixty.multiply(three);
|
||||
|
||||
let sixty_three_sq = sixty_three.multiply(sixty_three);
|
||||
let sixty_three_qu = sixty_three_sq.multiply(sixty_three_sq);
|
||||
let sixty_three_oc = sixty_three_qu.multiply(sixty_three_qu);
|
||||
let sixty_three_hx = sixty_three_oc.multiply(sixty_three_oc);
|
||||
let sixty_three_tt = sixty_three_hx.multiply(sixty_three_hx);
|
||||
let sixty_three_sf = sixty_three_tt.multiply(sixty_three_tt);
|
||||
|
||||
let all_ones = sixty_three_sf.multiply(sixty_three);
|
||||
let two_e_twelve = all_ones.multiply(self);
|
||||
let two_e_thirteen = two_e_twelve.multiply(two_e_twelve);
|
||||
|
||||
two_e_thirteen.multiply(all_ones)
|
||||
}
|
||||
}
|
||||
|
||||
impl CyclotomicFourier for FalconFelt {
|
||||
fn primitive_root_of_unity(n: usize) -> Self {
|
||||
let log2n = n.ilog2();
|
||||
assert!(log2n <= 12);
|
||||
// and 1331 is a twelfth root of unity
|
||||
let mut a = FalconFelt::new(1331);
|
||||
let num_squarings = 12 - n.ilog2();
|
||||
for _ in 0..num_squarings {
|
||||
a *= a;
|
||||
}
|
||||
a
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<u32> for FalconFelt {
|
||||
type Error = String;
|
||||
|
||||
fn try_from(value: u32) -> Result<Self, Self::Error> {
|
||||
if value >= MODULUS as u32 {
|
||||
Err(format!("value {value} is greater than or equal to the field modulus {MODULUS}"))
|
||||
} else {
|
||||
Ok(FalconFelt::new(value as i16))
|
||||
}
|
||||
}
|
||||
}
|
||||
320
src/dsa/rpo_falcon512/math/mod.rs
Normal file
320
src/dsa/rpo_falcon512/math/mod.rs
Normal file
@@ -0,0 +1,320 @@
|
||||
//! Contains different structs and methods related to the Falcon DSA.
|
||||
//!
|
||||
//! It uses and acknowledges the work in:
|
||||
//!
|
||||
//! 1. The [reference](https://falcon-sign.info/impl/README.txt.html) implementation by Thomas Pornin.
|
||||
//! 2. The [Rust](https://github.com/aszepieniec/falcon-rust) implementation by Alan Szepieniec.
|
||||
use super::MODULUS;
|
||||
use alloc::{string::String, vec::Vec};
|
||||
use core::ops::MulAssign;
|
||||
use num::{BigInt, FromPrimitive, One, Zero};
|
||||
use num_complex::Complex64;
|
||||
use rand::Rng;
|
||||
|
||||
#[cfg(not(feature = "std"))]
|
||||
use num::Float;
|
||||
|
||||
mod fft;
|
||||
pub use fft::{CyclotomicFourier, FastFft};
|
||||
|
||||
mod field;
|
||||
pub use field::FalconFelt;
|
||||
|
||||
mod ffsampling;
|
||||
pub use ffsampling::{ffldl, ffsampling, gram, normalize_tree, LdlTree};
|
||||
|
||||
mod samplerz;
|
||||
use self::samplerz::sampler_z;
|
||||
|
||||
mod polynomial;
|
||||
pub use polynomial::Polynomial;
|
||||
|
||||
pub trait Inverse: Copy + Zero + MulAssign + One {
|
||||
/// Gets the inverse of a, or zero if it is zero.
|
||||
fn inverse_or_zero(self) -> Self;
|
||||
|
||||
/// Gets the inverses of a batch of elements, and skip over any that are zero.
|
||||
fn batch_inverse_or_zero(batch: &[Self]) -> Vec<Self> {
|
||||
let mut acc = Self::one();
|
||||
let mut rp: Vec<Self> = Vec::with_capacity(batch.len());
|
||||
for batch_item in batch {
|
||||
if !batch_item.is_zero() {
|
||||
rp.push(acc);
|
||||
acc = *batch_item * acc;
|
||||
} else {
|
||||
rp.push(Self::zero());
|
||||
}
|
||||
}
|
||||
let mut inv = Self::inverse_or_zero(acc);
|
||||
for i in (0..batch.len()).rev() {
|
||||
if !batch[i].is_zero() {
|
||||
rp[i] *= inv;
|
||||
inv *= batch[i];
|
||||
}
|
||||
}
|
||||
rp
|
||||
}
|
||||
}
|
||||
|
||||
impl Inverse for Complex64 {
|
||||
fn inverse_or_zero(self) -> Self {
|
||||
let modulus = self.re * self.re + self.im * self.im;
|
||||
Complex64::new(self.re / modulus, -self.im / modulus)
|
||||
}
|
||||
fn batch_inverse_or_zero(batch: &[Self]) -> Vec<Self> {
|
||||
batch.iter().map(|&c| Complex64::new(1.0, 0.0) / c).collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl Inverse for f64 {
|
||||
fn inverse_or_zero(self) -> Self {
|
||||
1.0 / self
|
||||
}
|
||||
fn batch_inverse_or_zero(batch: &[Self]) -> Vec<Self> {
|
||||
batch.iter().map(|&c| 1.0 / c).collect()
|
||||
}
|
||||
}
|
||||
|
||||
/// Samples 4 small polynomials f, g, F, G such that f * G - g * F = q mod (X^n + 1).
|
||||
/// Algorithm 5 (NTRUgen) of the documentation [1, p.34].
|
||||
///
|
||||
/// [1]: https://falcon-sign.info/falcon.pdf
|
||||
pub(crate) fn ntru_gen<R: Rng>(n: usize, rng: &mut R) -> [Polynomial<i16>; 4] {
|
||||
loop {
|
||||
let f = gen_poly(n, rng);
|
||||
let g = gen_poly(n, rng);
|
||||
let f_ntt = f.map(|&i| FalconFelt::new(i)).fft();
|
||||
if f_ntt.coefficients.iter().any(|e| e.is_zero()) {
|
||||
continue;
|
||||
}
|
||||
let gamma = gram_schmidt_norm_squared(&f, &g);
|
||||
if gamma > 1.3689f64 * (MODULUS as f64) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some((capital_f, capital_g)) =
|
||||
ntru_solve(&f.map(|&i| i.into()), &g.map(|&i| i.into()))
|
||||
{
|
||||
return [
|
||||
f,
|
||||
g,
|
||||
capital_f.map(|i| i.try_into().unwrap()),
|
||||
capital_g.map(|i| i.try_into().unwrap()),
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Solves the NTRU equation. Given f, g in ZZ[X], find F, G in ZZ[X] such that:
|
||||
///
|
||||
/// f G - g F = q mod (X^n + 1)
|
||||
///
|
||||
/// Algorithm 6 of the specification [1, p.35].
|
||||
///
|
||||
/// [1]: https://falcon-sign.info/falcon.pdf
|
||||
fn ntru_solve(
|
||||
f: &Polynomial<BigInt>,
|
||||
g: &Polynomial<BigInt>,
|
||||
) -> Option<(Polynomial<BigInt>, Polynomial<BigInt>)> {
|
||||
let n = f.coefficients.len();
|
||||
if n == 1 {
|
||||
let (gcd, u, v) = xgcd(&f.coefficients[0], &g.coefficients[0]);
|
||||
if gcd != BigInt::one() {
|
||||
return None;
|
||||
}
|
||||
return Some((
|
||||
(Polynomial::new(vec![-v * BigInt::from_u32(MODULUS as u32).unwrap()])),
|
||||
Polynomial::new(vec![u * BigInt::from_u32(MODULUS as u32).unwrap()]),
|
||||
));
|
||||
}
|
||||
|
||||
let f_prime = f.field_norm();
|
||||
let g_prime = g.field_norm();
|
||||
|
||||
let (capital_f_prime, capital_g_prime) = ntru_solve(&f_prime, &g_prime)?;
|
||||
let capital_f_prime_xsq = capital_f_prime.lift_next_cyclotomic();
|
||||
let capital_g_prime_xsq = capital_g_prime.lift_next_cyclotomic();
|
||||
|
||||
let f_minx = f.galois_adjoint();
|
||||
let g_minx = g.galois_adjoint();
|
||||
|
||||
let mut capital_f = (capital_f_prime_xsq.karatsuba(&g_minx)).reduce_by_cyclotomic(n);
|
||||
let mut capital_g = (capital_g_prime_xsq.karatsuba(&f_minx)).reduce_by_cyclotomic(n);
|
||||
|
||||
match babai_reduce(f, g, &mut capital_f, &mut capital_g) {
|
||||
Ok(_) => Some((capital_f, capital_g)),
|
||||
Err(_e) => {
|
||||
#[cfg(test)]
|
||||
{
|
||||
panic!("{}", _e);
|
||||
}
|
||||
#[cfg(not(test))]
|
||||
{
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates a polynomial of degree at most n-1 whose coefficients are distributed according
|
||||
/// to a discrete Gaussian with mu = 0 and sigma = 1.17 * sqrt(Q / (2n)).
|
||||
fn gen_poly<R: Rng>(n: usize, rng: &mut R) -> Polynomial<i16> {
|
||||
let mu = 0.0;
|
||||
let sigma_star = 1.43300980528773;
|
||||
Polynomial {
|
||||
coefficients: (0..4096)
|
||||
.map(|_| sampler_z(mu, sigma_star, sigma_star - 0.001, rng))
|
||||
.collect::<Vec<i16>>()
|
||||
.chunks(4096 / n)
|
||||
.map(|ch| ch.iter().sum())
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Computes the Gram-Schmidt norm of B = [[g, -f], [G, -F]] from f and g.
|
||||
/// Corresponds to line 9 in algorithm 5 of the spec [1, p.34]
|
||||
///
|
||||
/// [1]: https://falcon-sign.info/falcon.pdf
|
||||
fn gram_schmidt_norm_squared(f: &Polynomial<i16>, g: &Polynomial<i16>) -> f64 {
|
||||
let n = f.coefficients.len();
|
||||
let norm_f_squared = f.l2_norm_squared();
|
||||
let norm_g_squared = g.l2_norm_squared();
|
||||
let gamma1 = norm_f_squared + norm_g_squared;
|
||||
|
||||
let f_fft = f.map(|i| Complex64::new(*i as f64, 0.0)).fft();
|
||||
let g_fft = g.map(|i| Complex64::new(*i as f64, 0.0)).fft();
|
||||
let f_adj_fft = f_fft.map(|c| c.conj());
|
||||
let g_adj_fft = g_fft.map(|c| c.conj());
|
||||
let ffgg_fft = f_fft.hadamard_mul(&f_adj_fft) + g_fft.hadamard_mul(&g_adj_fft);
|
||||
let ffgg_fft_inverse = ffgg_fft.hadamard_inv();
|
||||
let qf_over_ffgg_fft = f_adj_fft.map(|c| c * (MODULUS as f64)).hadamard_mul(&ffgg_fft_inverse);
|
||||
let qg_over_ffgg_fft = g_adj_fft.map(|c| c * (MODULUS as f64)).hadamard_mul(&ffgg_fft_inverse);
|
||||
let norm_f_over_ffgg_squared =
|
||||
qf_over_ffgg_fft.coefficients.iter().map(|c| (c * c.conj()).re).sum::<f64>() / (n as f64);
|
||||
let norm_g_over_ffgg_squared =
|
||||
qg_over_ffgg_fft.coefficients.iter().map(|c| (c * c.conj()).re).sum::<f64>() / (n as f64);
|
||||
|
||||
let gamma2 = norm_f_over_ffgg_squared + norm_g_over_ffgg_squared;
|
||||
|
||||
f64::max(gamma1, gamma2)
|
||||
}
|
||||
|
||||
/// Reduces the vector (F,G) relative to (f,g). This method follows the python implementation [1].
|
||||
/// Note that this algorithm can end up in an infinite loop. (It's one of the things the author
|
||||
/// would like to fix.) When this happens, control returns an error (hence the return type) and
|
||||
/// generates another keypair with fresh randomness.
|
||||
///
|
||||
/// Algorithm 7 in the spec [2, p.35]
|
||||
///
|
||||
/// [1]: https://github.com/tprest/falcon.py
|
||||
///
|
||||
/// [2]: https://falcon-sign.info/falcon.pdf
|
||||
fn babai_reduce(
|
||||
f: &Polynomial<BigInt>,
|
||||
g: &Polynomial<BigInt>,
|
||||
capital_f: &mut Polynomial<BigInt>,
|
||||
capital_g: &mut Polynomial<BigInt>,
|
||||
) -> Result<(), String> {
|
||||
let bitsize = |bi: &BigInt| (bi.bits() + 7) & (u64::MAX ^ 7);
|
||||
let n = f.coefficients.len();
|
||||
let size = [
|
||||
f.map(bitsize).fold(0, |a, &b| u64::max(a, b)),
|
||||
g.map(bitsize).fold(0, |a, &b| u64::max(a, b)),
|
||||
53,
|
||||
]
|
||||
.into_iter()
|
||||
.max()
|
||||
.unwrap();
|
||||
let shift = (size as i64) - 53;
|
||||
let f_adjusted = f
|
||||
.map(|bi| Complex64::new(i64::try_from(bi >> shift).unwrap() as f64, 0.0))
|
||||
.fft();
|
||||
let g_adjusted = g
|
||||
.map(|bi| Complex64::new(i64::try_from(bi >> shift).unwrap() as f64, 0.0))
|
||||
.fft();
|
||||
|
||||
let f_star_adjusted = f_adjusted.map(|c| c.conj());
|
||||
let g_star_adjusted = g_adjusted.map(|c| c.conj());
|
||||
let denominator_fft =
|
||||
f_adjusted.hadamard_mul(&f_star_adjusted) + g_adjusted.hadamard_mul(&g_star_adjusted);
|
||||
|
||||
let mut counter = 0;
|
||||
loop {
|
||||
let capital_size = [
|
||||
capital_f.map(bitsize).fold(0, |a, &b| u64::max(a, b)),
|
||||
capital_g.map(bitsize).fold(0, |a, &b| u64::max(a, b)),
|
||||
53,
|
||||
]
|
||||
.into_iter()
|
||||
.max()
|
||||
.unwrap();
|
||||
|
||||
if capital_size < size {
|
||||
break;
|
||||
}
|
||||
let capital_shift = (capital_size as i64) - 53;
|
||||
let capital_f_adjusted = capital_f
|
||||
.map(|bi| Complex64::new(i64::try_from(bi >> capital_shift).unwrap() as f64, 0.0))
|
||||
.fft();
|
||||
let capital_g_adjusted = capital_g
|
||||
.map(|bi| Complex64::new(i64::try_from(bi >> capital_shift).unwrap() as f64, 0.0))
|
||||
.fft();
|
||||
|
||||
let numerator = capital_f_adjusted.hadamard_mul(&f_star_adjusted)
|
||||
+ capital_g_adjusted.hadamard_mul(&g_star_adjusted);
|
||||
let quotient = numerator.hadamard_div(&denominator_fft).ifft();
|
||||
|
||||
let k = quotient.map(|f| Into::<BigInt>::into(f.re.round() as i64));
|
||||
|
||||
if k.is_zero() {
|
||||
break;
|
||||
}
|
||||
let kf = (k.clone().karatsuba(f))
|
||||
.reduce_by_cyclotomic(n)
|
||||
.map(|bi| bi << (capital_size - size));
|
||||
let kg = (k.clone().karatsuba(g))
|
||||
.reduce_by_cyclotomic(n)
|
||||
.map(|bi| bi << (capital_size - size));
|
||||
*capital_f -= kf;
|
||||
*capital_g -= kg;
|
||||
|
||||
counter += 1;
|
||||
if counter > 1000 {
|
||||
// If we get here, that means that (with high likelihood) we are in an
|
||||
// infinite loop. We know it happens from time to time -- seldomly, but it
|
||||
// does. It would be nice to fix that! But in order to fix it we need to be
|
||||
// able to reproduce it, and for that we need test vectors. So print them
|
||||
// and hope that one day they circle back to the implementor.
|
||||
return Err(format!("Encountered infinite loop in babai_reduce of falcon-rust.\n\\
|
||||
Please help the developer(s) fix it! You can do this by sending them the inputs to the function that caused the behavior:\n\\
|
||||
f: {:?}\n\\
|
||||
g: {:?}\n\\
|
||||
capital_f: {:?}\n\\
|
||||
capital_g: {:?}\n", f.coefficients, g.coefficients, capital_f.coefficients, capital_g.coefficients));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Extended Euclidean algorithm for computing the greatest common divisor (g) and
|
||||
/// Bézout coefficients (u, v) for the relation
|
||||
///
|
||||
/// $$ u a + v b = g . $$
|
||||
///
|
||||
/// Implementation adapted from Wikipedia [1].
|
||||
///
|
||||
/// [1]: https://en.wikipedia.org/wiki/Extended_Euclidean_algorithm#Pseudocode
|
||||
fn xgcd(a: &BigInt, b: &BigInt) -> (BigInt, BigInt, BigInt) {
|
||||
let (mut old_r, mut r) = (a.clone(), b.clone());
|
||||
let (mut old_s, mut s) = (BigInt::one(), BigInt::zero());
|
||||
let (mut old_t, mut t) = (BigInt::zero(), BigInt::one());
|
||||
|
||||
while r != BigInt::zero() {
|
||||
let quotient = old_r.clone() / r.clone();
|
||||
(old_r, r) = (r.clone(), old_r.clone() - quotient.clone() * r);
|
||||
(old_s, s) = (s.clone(), old_s.clone() - quotient.clone() * s);
|
||||
(old_t, t) = (t.clone(), old_t.clone() - quotient * t);
|
||||
}
|
||||
|
||||
(old_r, old_s, old_t)
|
||||
}
|
||||
616
src/dsa/rpo_falcon512/math/polynomial.rs
Normal file
616
src/dsa/rpo_falcon512/math/polynomial.rs
Normal file
@@ -0,0 +1,616 @@
|
||||
use super::{field::FalconFelt, Inverse};
|
||||
use crate::dsa::rpo_falcon512::{MODULUS, N};
|
||||
use crate::Felt;
|
||||
use alloc::vec::Vec;
|
||||
use core::default::Default;
|
||||
use core::fmt::Debug;
|
||||
use core::ops::{Add, AddAssign, Div, Mul, MulAssign, Neg, Sub, SubAssign};
|
||||
use num::{One, Zero};
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct Polynomial<F> {
|
||||
pub coefficients: Vec<F>,
|
||||
}
|
||||
|
||||
impl<F> Polynomial<F>
|
||||
where
|
||||
F: Clone,
|
||||
{
|
||||
pub fn new(coefficients: Vec<F>) -> Self {
|
||||
Self { coefficients }
|
||||
}
|
||||
}
|
||||
|
||||
impl<
|
||||
F: Mul<Output = F> + Sub<Output = F> + AddAssign + Zero + Div<Output = F> + Clone + Inverse,
|
||||
> Polynomial<F>
|
||||
{
|
||||
pub fn hadamard_mul(&self, other: &Self) -> Self {
|
||||
Polynomial::new(
|
||||
self.coefficients
|
||||
.iter()
|
||||
.zip(other.coefficients.iter())
|
||||
.map(|(a, b)| *a * *b)
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
pub fn hadamard_div(&self, other: &Self) -> Self {
|
||||
let other_coefficients_inverse = F::batch_inverse_or_zero(&other.coefficients);
|
||||
Polynomial::new(
|
||||
self.coefficients
|
||||
.iter()
|
||||
.zip(other_coefficients_inverse.iter())
|
||||
.map(|(a, b)| *a * *b)
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn hadamard_inv(&self) -> Self {
|
||||
let coefficients_inverse = F::batch_inverse_or_zero(&self.coefficients);
|
||||
Polynomial::new(coefficients_inverse)
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: Zero + PartialEq + Clone> Polynomial<F> {
|
||||
pub fn degree(&self) -> Option<usize> {
|
||||
if self.coefficients.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let mut max_index = self.coefficients.len() - 1;
|
||||
while self.coefficients[max_index] == F::zero() {
|
||||
if let Some(new_index) = max_index.checked_sub(1) {
|
||||
max_index = new_index;
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
Some(max_index)
|
||||
}
|
||||
|
||||
pub fn lc(&self) -> F {
|
||||
match self.degree() {
|
||||
Some(non_negative_degree) => self.coefficients[non_negative_degree].clone(),
|
||||
None => F::zero(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The following implementations are specific to cyclotomic polynomial rings,
|
||||
/// i.e., F\[ X \] / <X^n + 1>, and are used extensively in Falcon.
|
||||
impl<
|
||||
F: One
|
||||
+ Zero
|
||||
+ Clone
|
||||
+ Neg<Output = F>
|
||||
+ MulAssign
|
||||
+ AddAssign
|
||||
+ Div<Output = F>
|
||||
+ Sub<Output = F>
|
||||
+ PartialEq,
|
||||
> Polynomial<F>
|
||||
{
|
||||
/// Reduce the polynomial by X^n + 1.
|
||||
pub fn reduce_by_cyclotomic(&self, n: usize) -> Self {
|
||||
let mut coefficients = vec![F::zero(); n];
|
||||
let mut sign = -F::one();
|
||||
for (i, c) in self.coefficients.iter().cloned().enumerate() {
|
||||
if i % n == 0 {
|
||||
sign *= -F::one();
|
||||
}
|
||||
coefficients[i % n] += sign.clone() * c;
|
||||
}
|
||||
Polynomial::new(coefficients)
|
||||
}
|
||||
|
||||
/// Computes the field norm of the polynomial as an element of the cyclotomic ring
|
||||
/// F\[ X \] / <X^n + 1 > relative to one of half the size, i.e., F\[ X \] / <X^(n/2) + 1> .
|
||||
///
|
||||
/// Corresponds to formula 3.25 in the spec [1, p.30].
|
||||
///
|
||||
/// [1]: https://falcon-sign.info/falcon.pdf
|
||||
pub fn field_norm(&self) -> Self {
|
||||
let n = self.coefficients.len();
|
||||
let mut f0_coefficients = vec![F::zero(); n / 2];
|
||||
let mut f1_coefficients = vec![F::zero(); n / 2];
|
||||
for i in 0..n / 2 {
|
||||
f0_coefficients[i] = self.coefficients[2 * i].clone();
|
||||
f1_coefficients[i] = self.coefficients[2 * i + 1].clone();
|
||||
}
|
||||
let f0 = Polynomial::new(f0_coefficients);
|
||||
let f1 = Polynomial::new(f1_coefficients);
|
||||
let f0_squared = (f0.clone() * f0).reduce_by_cyclotomic(n / 2);
|
||||
let f1_squared = (f1.clone() * f1).reduce_by_cyclotomic(n / 2);
|
||||
let x = Polynomial::new(vec![F::zero(), F::one()]);
|
||||
f0_squared - (x * f1_squared).reduce_by_cyclotomic(n / 2)
|
||||
}
|
||||
|
||||
/// Lifts an element from a cyclotomic polynomial ring to one of double the size.
|
||||
pub fn lift_next_cyclotomic(&self) -> Self {
|
||||
let n = self.coefficients.len();
|
||||
let mut coefficients = vec![F::zero(); n * 2];
|
||||
for i in 0..n {
|
||||
coefficients[2 * i] = self.coefficients[i].clone();
|
||||
}
|
||||
Self::new(coefficients)
|
||||
}
|
||||
|
||||
/// Computes the galois adjoint of the polynomial in the cyclotomic ring F\[ X \] / < X^n + 1 > ,
|
||||
/// which corresponds to f(x^2).
|
||||
pub fn galois_adjoint(&self) -> Self {
|
||||
Self::new(
|
||||
self.coefficients
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, c)| if i % 2 == 0 { c.clone() } else { c.clone().neg() })
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: Clone + Into<f64>> Polynomial<F> {
|
||||
pub(crate) fn l2_norm_squared(&self) -> f64 {
|
||||
self.coefficients
|
||||
.iter()
|
||||
.map(|i| Into::<f64>::into(i.clone()))
|
||||
.map(|i| i * i)
|
||||
.sum::<f64>()
|
||||
}
|
||||
}
|
||||
|
||||
impl<F> PartialEq for Polynomial<F>
|
||||
where
|
||||
F: Zero + PartialEq + Clone + AddAssign,
|
||||
{
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
if self.is_zero() && other.is_zero() {
|
||||
true
|
||||
} else if self.is_zero() || other.is_zero() {
|
||||
false
|
||||
} else {
|
||||
let self_degree = self.degree().unwrap();
|
||||
let other_degree = other.degree().unwrap();
|
||||
self.coefficients[0..=self_degree] == other.coefficients[0..=other_degree]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<F> Eq for Polynomial<F> where F: Zero + PartialEq + Clone + AddAssign {}
|
||||
|
||||
impl<F> Add for &Polynomial<F>
|
||||
where
|
||||
F: Add<Output = F> + AddAssign + Clone,
|
||||
{
|
||||
type Output = Polynomial<F>;
|
||||
|
||||
fn add(self, rhs: Self) -> Self::Output {
|
||||
let coefficients = if self.coefficients.len() >= rhs.coefficients.len() {
|
||||
let mut coefficients = self.coefficients.clone();
|
||||
for (i, c) in rhs.coefficients.iter().enumerate() {
|
||||
coefficients[i] += c.clone();
|
||||
}
|
||||
coefficients
|
||||
} else {
|
||||
let mut coefficients = rhs.coefficients.clone();
|
||||
for (i, c) in self.coefficients.iter().enumerate() {
|
||||
coefficients[i] += c.clone();
|
||||
}
|
||||
coefficients
|
||||
};
|
||||
Self::Output { coefficients }
|
||||
}
|
||||
}
|
||||
|
||||
impl<F> Add for Polynomial<F>
|
||||
where
|
||||
F: Add<Output = F> + AddAssign + Clone,
|
||||
{
|
||||
type Output = Polynomial<F>;
|
||||
fn add(self, rhs: Self) -> Self::Output {
|
||||
let coefficients = if self.coefficients.len() >= rhs.coefficients.len() {
|
||||
let mut coefficients = self.coefficients.clone();
|
||||
for (i, c) in rhs.coefficients.into_iter().enumerate() {
|
||||
coefficients[i] += c;
|
||||
}
|
||||
coefficients
|
||||
} else {
|
||||
let mut coefficients = rhs.coefficients.clone();
|
||||
for (i, c) in self.coefficients.into_iter().enumerate() {
|
||||
coefficients[i] += c;
|
||||
}
|
||||
coefficients
|
||||
};
|
||||
Self::Output { coefficients }
|
||||
}
|
||||
}
|
||||
|
||||
impl<F> AddAssign for Polynomial<F>
|
||||
where
|
||||
F: Add<Output = F> + AddAssign + Clone,
|
||||
{
|
||||
fn add_assign(&mut self, rhs: Self) {
|
||||
if self.coefficients.len() >= rhs.coefficients.len() {
|
||||
for (i, c) in rhs.coefficients.into_iter().enumerate() {
|
||||
self.coefficients[i] += c;
|
||||
}
|
||||
} else {
|
||||
let mut coefficients = rhs.coefficients.clone();
|
||||
for (i, c) in self.coefficients.iter().enumerate() {
|
||||
coefficients[i] += c.clone();
|
||||
}
|
||||
self.coefficients = coefficients;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<F> Sub for &Polynomial<F>
|
||||
where
|
||||
F: Sub<Output = F> + Clone + Neg<Output = F> + Add<Output = F> + AddAssign,
|
||||
{
|
||||
type Output = Polynomial<F>;
|
||||
|
||||
fn sub(self, rhs: Self) -> Self::Output {
|
||||
self + &(-rhs)
|
||||
}
|
||||
}
|
||||
|
||||
impl<F> Sub for Polynomial<F>
|
||||
where
|
||||
F: Sub<Output = F> + Clone + Neg<Output = F> + Add<Output = F> + AddAssign,
|
||||
{
|
||||
type Output = Polynomial<F>;
|
||||
|
||||
fn sub(self, rhs: Self) -> Self::Output {
|
||||
self + (-rhs)
|
||||
}
|
||||
}
|
||||
|
||||
impl<F> SubAssign for Polynomial<F>
|
||||
where
|
||||
F: Add<Output = F> + Neg<Output = F> + AddAssign + Clone + Sub<Output = F>,
|
||||
{
|
||||
fn sub_assign(&mut self, rhs: Self) {
|
||||
self.coefficients = self.clone().sub(rhs).coefficients;
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: Neg<Output = F> + Clone> Neg for &Polynomial<F> {
|
||||
type Output = Polynomial<F>;
|
||||
|
||||
fn neg(self) -> Self::Output {
|
||||
Self::Output {
|
||||
coefficients: self.coefficients.iter().cloned().map(|a| -a).collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: Neg<Output = F> + Clone> Neg for Polynomial<F> {
|
||||
type Output = Self;
|
||||
|
||||
fn neg(self) -> Self::Output {
|
||||
Self::Output {
|
||||
coefficients: self.coefficients.iter().cloned().map(|a| -a).collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<F> Mul for &Polynomial<F>
|
||||
where
|
||||
F: Add + AddAssign + Mul<Output = F> + Sub<Output = F> + Zero + PartialEq + Clone,
|
||||
{
|
||||
type Output = Polynomial<F>;
|
||||
|
||||
fn mul(self, other: Self) -> Self::Output {
|
||||
if self.is_zero() || other.is_zero() {
|
||||
return Polynomial::<F>::zero();
|
||||
}
|
||||
let mut coefficients =
|
||||
vec![F::zero(); self.coefficients.len() + other.coefficients.len() - 1];
|
||||
for i in 0..self.coefficients.len() {
|
||||
for j in 0..other.coefficients.len() {
|
||||
coefficients[i + j] += self.coefficients[i].clone() * other.coefficients[j].clone();
|
||||
}
|
||||
}
|
||||
Polynomial { coefficients }
|
||||
}
|
||||
}
|
||||
|
||||
impl<F> Mul for Polynomial<F>
|
||||
where
|
||||
F: Add + AddAssign + Mul<Output = F> + Zero + PartialEq + Clone,
|
||||
{
|
||||
type Output = Self;
|
||||
|
||||
fn mul(self, other: Self) -> Self::Output {
|
||||
if self.is_zero() || other.is_zero() {
|
||||
return Self::zero();
|
||||
}
|
||||
let mut coefficients =
|
||||
vec![F::zero(); self.coefficients.len() + other.coefficients.len() - 1];
|
||||
for i in 0..self.coefficients.len() {
|
||||
for j in 0..other.coefficients.len() {
|
||||
coefficients[i + j] += self.coefficients[i].clone() * other.coefficients[j].clone();
|
||||
}
|
||||
}
|
||||
Self { coefficients }
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: Add + Mul<Output = F> + Zero + Clone> Mul<F> for &Polynomial<F> {
|
||||
type Output = Polynomial<F>;
|
||||
|
||||
fn mul(self, other: F) -> Self::Output {
|
||||
Polynomial {
|
||||
coefficients: self.coefficients.iter().cloned().map(|i| i * other.clone()).collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: Add + Mul<Output = F> + Zero + Clone> Mul<F> for Polynomial<F> {
|
||||
type Output = Polynomial<F>;
|
||||
|
||||
fn mul(self, other: F) -> Self::Output {
|
||||
Polynomial {
|
||||
coefficients: self.coefficients.iter().cloned().map(|i| i * other.clone()).collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: Mul<Output = F> + Sub<Output = F> + AddAssign + Zero + Div<Output = F> + Clone>
|
||||
Polynomial<F>
|
||||
{
|
||||
/// Multiply two polynomials using Karatsuba's divide-and-conquer algorithm.
|
||||
pub fn karatsuba(&self, other: &Self) -> Self {
|
||||
Polynomial::new(vector_karatsuba(&self.coefficients, &other.coefficients))
|
||||
}
|
||||
}
|
||||
|
||||
impl<F> One for Polynomial<F>
|
||||
where
|
||||
F: Clone + One + PartialEq + Zero + AddAssign,
|
||||
{
|
||||
fn one() -> Self {
|
||||
Self { coefficients: vec![F::one()] }
|
||||
}
|
||||
}
|
||||
|
||||
impl<F> Zero for Polynomial<F>
|
||||
where
|
||||
F: Zero + PartialEq + Clone + AddAssign,
|
||||
{
|
||||
fn zero() -> Self {
|
||||
Self { coefficients: vec![] }
|
||||
}
|
||||
|
||||
fn is_zero(&self) -> bool {
|
||||
self.degree().is_none()
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: Zero + Clone> Polynomial<F> {
|
||||
pub fn shift(&self, shamt: usize) -> Self {
|
||||
Self {
|
||||
coefficients: [vec![F::zero(); shamt], self.coefficients.clone()].concat(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn constant(f: F) -> Self {
|
||||
Self { coefficients: vec![f] }
|
||||
}
|
||||
|
||||
pub fn map<G: Clone, C: FnMut(&F) -> G>(&self, closure: C) -> Polynomial<G> {
|
||||
Polynomial::<G>::new(self.coefficients.iter().map(closure).collect())
|
||||
}
|
||||
|
||||
pub fn fold<G, C: FnMut(G, &F) -> G + Clone>(&self, mut initial_value: G, closure: C) -> G {
|
||||
for c in self.coefficients.iter() {
|
||||
initial_value = (closure.clone())(initial_value, c);
|
||||
}
|
||||
initial_value
|
||||
}
|
||||
}
|
||||
|
||||
impl<F> Div<Polynomial<F>> for Polynomial<F>
|
||||
where
|
||||
F: Zero
|
||||
+ One
|
||||
+ PartialEq
|
||||
+ AddAssign
|
||||
+ Clone
|
||||
+ Mul<Output = F>
|
||||
+ MulAssign
|
||||
+ Div<Output = F>
|
||||
+ Neg<Output = F>
|
||||
+ Sub<Output = F>,
|
||||
{
|
||||
type Output = Polynomial<F>;
|
||||
|
||||
fn div(self, denominator: Self) -> Self::Output {
|
||||
if denominator.is_zero() {
|
||||
panic!();
|
||||
}
|
||||
if self.is_zero() {
|
||||
Self::zero();
|
||||
}
|
||||
let mut remainder = self.clone();
|
||||
let mut quotient = Polynomial::<F>::zero();
|
||||
while remainder.degree().unwrap() >= denominator.degree().unwrap() {
|
||||
let shift = remainder.degree().unwrap() - denominator.degree().unwrap();
|
||||
let quotient_coefficient = remainder.lc() / denominator.lc();
|
||||
let monomial = Self::constant(quotient_coefficient).shift(shift);
|
||||
quotient += monomial.clone();
|
||||
remainder -= monomial * denominator.clone();
|
||||
if remainder.is_zero() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
quotient
|
||||
}
|
||||
}
|
||||
|
||||
fn vector_karatsuba<
|
||||
F: Zero + AddAssign + Mul<Output = F> + Sub<Output = F> + Div<Output = F> + Clone,
|
||||
>(
|
||||
left: &[F],
|
||||
right: &[F],
|
||||
) -> Vec<F> {
|
||||
let n = left.len();
|
||||
if n <= 8 {
|
||||
let mut product = vec![F::zero(); left.len() + right.len() - 1];
|
||||
for (i, l) in left.iter().enumerate() {
|
||||
for (j, r) in right.iter().enumerate() {
|
||||
product[i + j] += l.clone() * r.clone();
|
||||
}
|
||||
}
|
||||
return product;
|
||||
}
|
||||
let n_over_2 = n / 2;
|
||||
let mut product = vec![F::zero(); 2 * n - 1];
|
||||
let left_lo = &left[0..n_over_2];
|
||||
let right_lo = &right[0..n_over_2];
|
||||
let left_hi = &left[n_over_2..];
|
||||
let right_hi = &right[n_over_2..];
|
||||
let left_sum: Vec<F> =
|
||||
left_lo.iter().zip(left_hi).map(|(a, b)| a.clone() + b.clone()).collect();
|
||||
let right_sum: Vec<F> =
|
||||
right_lo.iter().zip(right_hi).map(|(a, b)| a.clone() + b.clone()).collect();
|
||||
|
||||
let prod_lo = vector_karatsuba(left_lo, right_lo);
|
||||
let prod_hi = vector_karatsuba(left_hi, right_hi);
|
||||
let prod_mid: Vec<F> = vector_karatsuba(&left_sum, &right_sum)
|
||||
.iter()
|
||||
.zip(prod_lo.iter().zip(prod_hi.iter()))
|
||||
.map(|(s, (l, h))| s.clone() - (l.clone() + h.clone()))
|
||||
.collect();
|
||||
|
||||
for (i, l) in prod_lo.into_iter().enumerate() {
|
||||
product[i] = l;
|
||||
}
|
||||
for (i, m) in prod_mid.into_iter().enumerate() {
|
||||
product[i + n_over_2] += m;
|
||||
}
|
||||
for (i, h) in prod_hi.into_iter().enumerate() {
|
||||
product[i + n] += h
|
||||
}
|
||||
product
|
||||
}
|
||||
|
||||
impl From<Polynomial<FalconFelt>> for Polynomial<Felt> {
|
||||
fn from(item: Polynomial<FalconFelt>) -> Self {
|
||||
let res: Vec<Felt> =
|
||||
item.coefficients.iter().map(|a| Felt::from(a.value() as u16)).collect();
|
||||
Polynomial::new(res)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Polynomial<FalconFelt>> for Polynomial<Felt> {
|
||||
fn from(item: &Polynomial<FalconFelt>) -> Self {
|
||||
let res: Vec<Felt> =
|
||||
item.coefficients.iter().map(|a| Felt::from(a.value() as u16)).collect();
|
||||
Polynomial::new(res)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Polynomial<i16>> for Polynomial<FalconFelt> {
|
||||
fn from(item: Polynomial<i16>) -> Self {
|
||||
let res: Vec<FalconFelt> = item.coefficients.iter().map(|&a| FalconFelt::new(a)).collect();
|
||||
Polynomial::new(res)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Polynomial<i16>> for Polynomial<FalconFelt> {
|
||||
fn from(item: &Polynomial<i16>) -> Self {
|
||||
let res: Vec<FalconFelt> = item.coefficients.iter().map(|&a| FalconFelt::new(a)).collect();
|
||||
Polynomial::new(res)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<i16>> for Polynomial<FalconFelt> {
|
||||
fn from(item: Vec<i16>) -> Self {
|
||||
let res: Vec<FalconFelt> = item.iter().map(|&a| FalconFelt::new(a)).collect();
|
||||
Polynomial::new(res)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Vec<i16>> for Polynomial<FalconFelt> {
|
||||
fn from(item: &Vec<i16>) -> Self {
|
||||
let res: Vec<FalconFelt> = item.iter().map(|&a| FalconFelt::new(a)).collect();
|
||||
Polynomial::new(res)
|
||||
}
|
||||
}
|
||||
|
||||
impl Polynomial<FalconFelt> {
|
||||
pub fn norm_squared(&self) -> u64 {
|
||||
self.coefficients
|
||||
.iter()
|
||||
.map(|&i| i.balanced_value() as i64)
|
||||
.map(|i| (i * i) as u64)
|
||||
.sum::<u64>()
|
||||
}
|
||||
|
||||
// PUBLIC ACCESSORS
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
/// Returns the coefficients of this polynomial as field elements.
|
||||
pub fn to_elements(&self) -> Vec<Felt> {
|
||||
self.coefficients.iter().map(|&a| Felt::from(a.value() as u16)).collect()
|
||||
}
|
||||
|
||||
// POLYNOMIAL OPERATIONS
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
/// Multiplies two polynomials over Z_p\[x\] without reducing modulo p. Given that the degrees
|
||||
/// of the input polynomials are less than 512 and their coefficients are less than the modulus
|
||||
/// q equal to 12289, the resulting product polynomial is guaranteed to have coefficients less
|
||||
/// than the Miden prime.
|
||||
///
|
||||
/// Note that this multiplication is not over Z_p\[x\]/(phi).
|
||||
pub fn mul_modulo_p(a: &Self, b: &Self) -> [u64; 1024] {
|
||||
let mut c = [0; 2 * N];
|
||||
for i in 0..N {
|
||||
for j in 0..N {
|
||||
c[i + j] += a.coefficients[i].value() as u64 * b.coefficients[j].value() as u64;
|
||||
}
|
||||
}
|
||||
|
||||
c
|
||||
}
|
||||
|
||||
/// Reduces a polynomial, that is the product of two polynomials over Z_p\[x\], modulo
|
||||
/// the irreducible polynomial phi. This results in an element in Z_p\[x\]/(phi).
|
||||
pub fn reduce_negacyclic(a: &[u64; 1024]) -> Self {
|
||||
let mut c = [FalconFelt::zero(); N];
|
||||
let modulus = MODULUS as u16;
|
||||
for i in 0..N {
|
||||
let ai = a[N + i] % modulus as u64;
|
||||
let neg_ai = (modulus - ai as u16) % modulus;
|
||||
|
||||
let bi = (a[i] % modulus as u64) as u16;
|
||||
c[i] = FalconFelt::new(((neg_ai + bi) % modulus) as i16);
|
||||
}
|
||||
|
||||
Self::new(c.to_vec())
|
||||
}
|
||||
}
|
||||
|
||||
// TESTS
|
||||
// ================================================================================================
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{FalconFelt, Polynomial, N};
|
||||
|
||||
#[test]
|
||||
fn test_negacyclic_reduction() {
|
||||
let coef1: [u8; N] = rand_utils::rand_array();
|
||||
let coef2: [u8; N] = rand_utils::rand_array();
|
||||
|
||||
let poly1 = Polynomial::new(coef1.iter().map(|&a| FalconFelt::new(a as i16)).collect());
|
||||
let poly2 = Polynomial::new(coef2.iter().map(|&a| FalconFelt::new(a as i16)).collect());
|
||||
let prod = poly1.clone() * poly2.clone();
|
||||
|
||||
assert_eq!(
|
||||
prod.reduce_by_cyclotomic(N),
|
||||
Polynomial::reduce_negacyclic(&Polynomial::mul_modulo_p(&poly1, &poly2))
|
||||
);
|
||||
}
|
||||
}
|
||||
298
src/dsa/rpo_falcon512/math/samplerz.rs
Normal file
298
src/dsa/rpo_falcon512/math/samplerz.rs
Normal file
@@ -0,0 +1,298 @@
|
||||
use core::f64::consts::LN_2;
|
||||
use rand::Rng;
|
||||
|
||||
#[cfg(not(feature = "std"))]
|
||||
use num::Float;
|
||||
|
||||
/// Samples an integer from {0, ..., 18} according to the distribution χ, which is close to
|
||||
/// the half-Gaussian distribution on the natural numbers with mean 0 and standard deviation
|
||||
/// equal to sigma_max.
|
||||
fn base_sampler(bytes: [u8; 9]) -> i16 {
|
||||
const RCDT: [u128; 18] = [
|
||||
3024686241123004913666,
|
||||
1564742784480091954050,
|
||||
636254429462080897535,
|
||||
199560484645026482916,
|
||||
47667343854657281903,
|
||||
8595902006365044063,
|
||||
1163297957344668388,
|
||||
117656387352093658,
|
||||
8867391802663976,
|
||||
496969357462633,
|
||||
20680885154299,
|
||||
638331848991,
|
||||
14602316184,
|
||||
247426747,
|
||||
3104126,
|
||||
28824,
|
||||
198,
|
||||
1,
|
||||
];
|
||||
let u = u128::from_be_bytes([vec![0u8; 7], bytes.to_vec()].concat().try_into().unwrap());
|
||||
RCDT.into_iter().filter(|r| u < *r).count() as i16
|
||||
}
|
||||
|
||||
/// Computes an integer approximation of 2^63 * ccs * exp(-x).
|
||||
fn approx_exp(x: f64, ccs: f64) -> u64 {
|
||||
// The constants C are used to approximate exp(-x); these
|
||||
// constants are taken from FACCT (up to a scaling factor
|
||||
// of 2^63):
|
||||
// https://eprint.iacr.org/2018/1234
|
||||
// https://github.com/raykzhao/gaussian
|
||||
const C: [u64; 13] = [
|
||||
0x00000004741183A3u64,
|
||||
0x00000036548CFC06u64,
|
||||
0x0000024FDCBF140Au64,
|
||||
0x0000171D939DE045u64,
|
||||
0x0000D00CF58F6F84u64,
|
||||
0x000680681CF796E3u64,
|
||||
0x002D82D8305B0FEAu64,
|
||||
0x011111110E066FD0u64,
|
||||
0x0555555555070F00u64,
|
||||
0x155555555581FF00u64,
|
||||
0x400000000002B400u64,
|
||||
0x7FFFFFFFFFFF4800u64,
|
||||
0x8000000000000000u64,
|
||||
];
|
||||
|
||||
let mut z: u64;
|
||||
let mut y: u64;
|
||||
let twoe63 = 1u64 << 63;
|
||||
|
||||
y = C[0];
|
||||
z = f64::floor(x * (twoe63 as f64)) as u64;
|
||||
for cu in C.iter().skip(1) {
|
||||
let zy = (z as u128) * (y as u128);
|
||||
y = cu - ((zy >> 63) as u64);
|
||||
}
|
||||
|
||||
z = f64::floor((twoe63 as f64) * ccs) as u64;
|
||||
|
||||
(((z as u128) * (y as u128)) >> 63) as u64
|
||||
}
|
||||
|
||||
/// A random bool that is true with probability ≈ ccs · exp(-x).
|
||||
fn ber_exp(x: f64, ccs: f64, random_bytes: [u8; 7]) -> bool {
|
||||
// 0.69314718055994530941 = ln(2)
|
||||
let s = f64::floor(x / LN_2) as usize;
|
||||
let r = x - LN_2 * (s as f64);
|
||||
let shamt = usize::min(s, 63);
|
||||
let z = ((((approx_exp(r, ccs) as u128) << 1) - 1) >> shamt) as u64;
|
||||
let mut w = 0i16;
|
||||
for (index, i) in (0..64).step_by(8).rev().enumerate() {
|
||||
let byte = random_bytes[index];
|
||||
w = (byte as i16) - (((z >> i) & 0xff) as i16);
|
||||
if w != 0 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
w < 0
|
||||
}
|
||||
|
||||
/// Samples an integer from the Gaussian distribution with given mean (mu) and standard deviation
|
||||
/// (sigma).
|
||||
pub(crate) fn sampler_z<R: Rng>(mu: f64, sigma: f64, sigma_min: f64, rng: &mut R) -> i16 {
|
||||
const SIGMA_MAX: f64 = 1.8205;
|
||||
const INV_2SIGMA_MAX_SQ: f64 = 1f64 / (2f64 * SIGMA_MAX * SIGMA_MAX);
|
||||
let isigma = 1f64 / sigma;
|
||||
let dss = 0.5f64 * isigma * isigma;
|
||||
let s = f64::floor(mu);
|
||||
let r = mu - s;
|
||||
let ccs = sigma_min * isigma;
|
||||
loop {
|
||||
let z0 = base_sampler(rng.gen());
|
||||
let random_byte: u8 = rng.gen();
|
||||
let b = (random_byte & 1) as i16;
|
||||
let z = b + ((b << 1) - 1) * z0;
|
||||
let zf_min_r = (z as f64) - r;
|
||||
// x = ((z-r)^2)/(2*sigma^2) - ((z-b)^2)/(2*sigma0^2)
|
||||
let x = zf_min_r * zf_min_r * dss - (z0 * z0) as f64 * INV_2SIGMA_MAX_SQ;
|
||||
if ber_exp(x, ccs, rng.gen()) {
|
||||
return z + (s as i16);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(test, feature = "std"))]
|
||||
mod test {
|
||||
use alloc::vec::Vec;
|
||||
use rand::RngCore;
|
||||
use std::{thread::sleep, time::Duration};
|
||||
|
||||
use super::{approx_exp, ber_exp, sampler_z};
|
||||
|
||||
/// RNG used only for testing purposes, whereby the produced
|
||||
/// string of random bytes is equal to the one it is initialized
|
||||
/// with. Whatever you do, do not use this RNG in production.
|
||||
struct UnsafeBufferRng {
|
||||
buffer: Vec<u8>,
|
||||
index: usize,
|
||||
}
|
||||
|
||||
impl UnsafeBufferRng {
|
||||
fn new(buffer: &[u8]) -> Self {
|
||||
Self { buffer: buffer.to_vec(), index: 0 }
|
||||
}
|
||||
|
||||
fn next(&mut self) -> u8 {
|
||||
if self.buffer.len() <= self.index {
|
||||
// panic!("Ran out of buffer.");
|
||||
sleep(Duration::from_millis(10));
|
||||
0u8
|
||||
} else {
|
||||
let return_value = self.buffer[self.index];
|
||||
self.index += 1;
|
||||
return_value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RngCore for UnsafeBufferRng {
|
||||
fn next_u32(&mut self) -> u32 {
|
||||
// let bytes: [u8; 4] = (0..4)
|
||||
// .map(|_| self.next())
|
||||
// .collect_vec()
|
||||
// .try_into()
|
||||
// .unwrap();
|
||||
// u32::from_be_bytes(bytes)
|
||||
u32::from_le_bytes([self.next(), 0, 0, 0])
|
||||
}
|
||||
|
||||
fn next_u64(&mut self) -> u64 {
|
||||
// let bytes: [u8; 8] = (0..8)
|
||||
// .map(|_| self.next())
|
||||
// .collect_vec()
|
||||
// .try_into()
|
||||
// .unwrap();
|
||||
// u64::from_be_bytes(bytes)
|
||||
u64::from_le_bytes([self.next(), 0, 0, 0, 0, 0, 0, 0])
|
||||
}
|
||||
|
||||
fn fill_bytes(&mut self, dest: &mut [u8]) {
|
||||
for d in dest.iter_mut() {
|
||||
*d = self.next();
|
||||
}
|
||||
}
|
||||
|
||||
fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand::Error> {
|
||||
for d in dest.iter_mut() {
|
||||
*d = self.next();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_unsafe_buffer_rng() {
|
||||
let seed_bytes = hex::decode("7FFECD162AE2").unwrap();
|
||||
let mut rng = UnsafeBufferRng::new(&seed_bytes);
|
||||
let generated_bytes: Vec<u8> = (0..seed_bytes.len()).map(|_| rng.next()).collect();
|
||||
assert_eq!(seed_bytes, generated_bytes);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_approx_exp() {
|
||||
let precision = 1u64 << 14;
|
||||
// known answers were generated with the following sage script:
|
||||
//```sage
|
||||
// num_samples = 10
|
||||
// precision = 200
|
||||
// R = Reals(precision)
|
||||
//
|
||||
// print(f"let kats : [(f64, f64, u64);{num_samples}] = [")
|
||||
// for i in range(num_samples):
|
||||
// x = RDF.random_element(0.0, 0.693147180559945)
|
||||
// ccs = RDF.random_element(0.0, 1.0)
|
||||
// res = round(2^63 * R(ccs) * exp(R(-x)))
|
||||
// print(f"({x}, {ccs}, {res}),")
|
||||
// print("];")
|
||||
// ```
|
||||
let kats: [(f64, f64, u64); 10] = [
|
||||
(0.2314993926072656, 0.8148006314615972, 5962140072160879737),
|
||||
(0.2648875572812225, 0.12769669655309035, 903712282351034505),
|
||||
(0.11251957513682391, 0.9264611470305881, 7635725498677341553),
|
||||
(0.04353439307256617, 0.5306497137523327, 4685877322232397936),
|
||||
(0.41834495299784347, 0.879438856118578, 5338392138535350986),
|
||||
(0.32579398973228557, 0.16513412873289002, 1099603299296456803),
|
||||
(0.5939508073919817, 0.029776019144967303, 151637565622779016),
|
||||
(0.2932367999399056, 0.37123847662857923, 2553827649386670452),
|
||||
(0.5005699297417507, 0.31447208863888976, 1758235618083658825),
|
||||
(0.4876437338498085, 0.6159515298936868, 3488632981903743976),
|
||||
];
|
||||
for (x, ccs, answer) in kats {
|
||||
let difference = (answer as i128) - (approx_exp(x, ccs) as i128);
|
||||
assert!(
|
||||
(difference * difference) as u64 <= precision * precision,
|
||||
"answer: {answer} versus approximation: {}\ndifference: {} whereas precision: {}",
|
||||
approx_exp(x, ccs),
|
||||
difference,
|
||||
precision
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ber_exp() {
|
||||
let kats = [
|
||||
(
|
||||
1.268_314_048_020_498_4,
|
||||
0.749_990_853_267_664_9,
|
||||
hex::decode("ea000000000000").unwrap(),
|
||||
false,
|
||||
),
|
||||
(
|
||||
0.001_563_917_959_143_409_6,
|
||||
0.749_990_853_267_664_9,
|
||||
hex::decode("6c000000000000").unwrap(),
|
||||
true,
|
||||
),
|
||||
(
|
||||
0.017_921_215_753_999_235,
|
||||
0.749_990_853_267_664_9,
|
||||
hex::decode("c2000000000000").unwrap(),
|
||||
false,
|
||||
),
|
||||
(
|
||||
0.776_117_648_844_980_6,
|
||||
0.751_181_554_542_520_8,
|
||||
hex::decode("58000000000000").unwrap(),
|
||||
true,
|
||||
),
|
||||
];
|
||||
for (x, ccs, bytes, answer) in kats {
|
||||
assert_eq!(answer, ber_exp(x, ccs, bytes.try_into().unwrap()));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sampler_z() {
|
||||
let sigma_min = 1.277833697;
|
||||
// known answers from the doc, table 3.2, page 44
|
||||
// https://falcon-sign.info/falcon.pdf
|
||||
// The zeros were added to account for dropped bytes.
|
||||
let kats = [
|
||||
(-91.90471153063714,1.7037990414754918,hex::decode("0fc5442ff043d66e91d1ea000000000000cac64ea5450a22941edc6c").unwrap(),-92),
|
||||
(-8.322564895434937,1.7037990414754918,hex::decode("f4da0f8d8444d1a77265c2000000000000ef6f98bbbb4bee7db8d9b3").unwrap(),-8),
|
||||
(-19.096516109216804,1.7035823083824078,hex::decode("db47f6d7fb9b19f25c36d6000000000000b9334d477a8bc0be68145d").unwrap(),-20),
|
||||
(-11.335543982423326, 1.7035823083824078, hex::decode("ae41b4f5209665c74d00dc000000000000c1a8168a7bb516b3190cb42c1ded26cd52000000000000aed770eca7dd334e0547bcc3c163ce0b").unwrap(), -12),
|
||||
(7.9386734193997555, 1.6984647769450156, hex::decode("31054166c1012780c603ae0000000000009b833cec73f2f41ca5807c000000000000c89c92158834632f9b1555").unwrap(), 8),
|
||||
(-28.990850086867255, 1.6984647769450156, hex::decode("737e9d68a50a06dbbc6477").unwrap(), -30),
|
||||
(-9.071257914091655, 1.6980782114808988, hex::decode("a98ddd14bf0bf22061d632").unwrap(), -10),
|
||||
(-43.88754568839566, 1.6980782114808988, hex::decode("3cbf6818a68f7ab9991514").unwrap(), -41),
|
||||
(-58.17435547946095,1.7010983419195522,hex::decode("6f8633f5bfa5d26848668e0000000000003d5ddd46958e97630410587c").unwrap(),-61),
|
||||
(-43.58664906684732, 1.7010983419195522, hex::decode("272bc6c25f5c5ee53f83c40000000000003a361fbc7cc91dc783e20a").unwrap(), -46),
|
||||
(-34.70565203313315, 1.7009387219711465, hex::decode("45443c59574c2c3b07e2e1000000000000d9071e6d133dbe32754b0a").unwrap(), -34),
|
||||
(-44.36009577368896, 1.7009387219711465, hex::decode("6ac116ed60c258e2cbaeab000000000000728c4823e6da36e18d08da0000000000005d0cc104e21cc7fd1f5ca8000000000000d9dbb675266c928448059e").unwrap(), -44),
|
||||
(-21.783037079346236, 1.6958406126012802, hex::decode("68163bc1e2cbf3e18e7426").unwrap(), -23),
|
||||
(-39.68827784633828, 1.6958406126012802, hex::decode("d6a1b51d76222a705a0259").unwrap(), -40),
|
||||
(-18.488607061056847, 1.6955259305261838, hex::decode("f0523bfaa8a394bf4ea5c10000000000000f842366fde286d6a30803").unwrap(), -22),
|
||||
(-48.39610939101591, 1.6955259305261838, hex::decode("87bd87e63374cee62127fc0000000000006931104aab64f136a0485b").unwrap(), -50),
|
||||
];
|
||||
for (mu, sigma, random_bytes, answer) in kats {
|
||||
assert_eq!(
|
||||
sampler_z(mu, sigma, sigma_min, &mut UnsafeBufferRng::new(&random_bytes)),
|
||||
answer
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,33 +4,31 @@ use crate::{
|
||||
Felt, Word, ZERO,
|
||||
};
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
mod ffi;
|
||||
|
||||
mod error;
|
||||
mod hash_to_point;
|
||||
mod keys;
|
||||
mod polynomial;
|
||||
mod math;
|
||||
mod signature;
|
||||
|
||||
pub use error::FalconError;
|
||||
pub use keys::{KeyPair, PublicKey};
|
||||
pub use polynomial::Polynomial;
|
||||
pub use signature::Signature;
|
||||
pub use self::keys::{PubKeyPoly, PublicKey, SecretKey};
|
||||
pub use self::math::Polynomial;
|
||||
pub use self::signature::{Signature, SignatureHeader, SignaturePoly};
|
||||
|
||||
// CONSTANTS
|
||||
// ================================================================================================
|
||||
|
||||
// The Falcon modulus.
|
||||
const MODULUS: u16 = 12289;
|
||||
const MODULUS_MINUS_1_OVER_TWO: u16 = 6144;
|
||||
// The Falcon modulus p.
|
||||
const MODULUS: i16 = 12289;
|
||||
|
||||
// Number of bits needed to encode an element in the Falcon field.
|
||||
const FALCON_ENCODING_BITS: u32 = 14;
|
||||
|
||||
// The Falcon parameters for Falcon-512. This is the degree of the polynomial `phi := x^N + 1`
|
||||
// defining the ring Z_p[x]/(phi).
|
||||
const N: usize = 512;
|
||||
const LOG_N: usize = 9;
|
||||
const LOG_N: u8 = 9;
|
||||
|
||||
/// Length of nonce used for key-pair generation.
|
||||
const NONCE_LEN: usize = 40;
|
||||
const SIG_NONCE_LEN: usize = 40;
|
||||
|
||||
/// Number of filed elements used to encode a nonce.
|
||||
const NONCE_ELEMENTS: usize = 8;
|
||||
@@ -42,16 +40,64 @@ pub const PK_LEN: usize = 897;
|
||||
pub const SK_LEN: usize = 1281;
|
||||
|
||||
/// Signature length as a u8 vector.
|
||||
const SIG_LEN: usize = 626;
|
||||
const SIG_POLY_BYTE_LEN: usize = 625;
|
||||
|
||||
/// Bound on the squared-norm of the signature.
|
||||
const SIG_L2_BOUND: u64 = 34034726;
|
||||
|
||||
/// Standard deviation of the Gaussian over the lattice.
|
||||
const SIGMA: f64 = 165.7366171829776;
|
||||
|
||||
// TYPE ALIASES
|
||||
// ================================================================================================
|
||||
|
||||
type SignatureBytes = [u8; NONCE_LEN + SIG_LEN];
|
||||
type PublicKeyBytes = [u8; PK_LEN];
|
||||
type SecretKeyBytes = [u8; SK_LEN];
|
||||
type NonceBytes = [u8; NONCE_LEN];
|
||||
type NonceElements = [Felt; NONCE_ELEMENTS];
|
||||
type ShortLatticeBasis = [Polynomial<i16>; 4];
|
||||
|
||||
// NONCE
|
||||
// ================================================================================================
|
||||
|
||||
/// Nonce of the Falcon signature.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Nonce([u8; SIG_NONCE_LEN]);
|
||||
|
||||
impl Nonce {
|
||||
/// Returns a new [Nonce] instantiated from the provided bytes.
|
||||
pub fn new(bytes: [u8; SIG_NONCE_LEN]) -> Self {
|
||||
Self(bytes)
|
||||
}
|
||||
|
||||
/// Returns the underlying bytes of this nonce.
|
||||
pub fn as_bytes(&self) -> &[u8; SIG_NONCE_LEN] {
|
||||
&self.0
|
||||
}
|
||||
|
||||
/// Converts byte representation of the nonce into field element representation.
|
||||
///
|
||||
/// Nonce bytes are converted to field elements by taking consecutive 5 byte chunks
|
||||
/// of the nonce and interpreting them as field elements.
|
||||
pub fn to_elements(&self) -> [Felt; NONCE_ELEMENTS] {
|
||||
let mut buffer = [0_u8; 8];
|
||||
let mut result = [ZERO; 8];
|
||||
for (i, bytes) in self.0.chunks(5).enumerate() {
|
||||
buffer[..5].copy_from_slice(bytes);
|
||||
// we can safely (without overflow) create a new Felt from u64 value here since this
|
||||
// value contains at most 5 bytes
|
||||
result[i] = Felt::new(u64::from_le_bytes(buffer));
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
impl Serializable for &Nonce {
|
||||
fn write_into<W: ByteWriter>(&self, target: &mut W) {
|
||||
target.write_bytes(&self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Deserializable for Nonce {
|
||||
fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
|
||||
let bytes = source.read()?;
|
||||
Ok(Self(bytes))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,279 +0,0 @@
|
||||
use alloc::vec::Vec;
|
||||
use core::ops::{Add, Mul, Sub};
|
||||
|
||||
use super::{FalconError, Felt, LOG_N, MODULUS, MODULUS_MINUS_1_OVER_TWO, N, PK_LEN};
|
||||
|
||||
// FALCON POLYNOMIAL
|
||||
// ================================================================================================
|
||||
|
||||
/// A polynomial over Z_p\[x\]/(phi) where phi := x^512 + 1
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
pub struct Polynomial([u16; N]);
|
||||
|
||||
impl Polynomial {
|
||||
// CONSTRUCTORS
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
/// Constructs a new polynomial from a list of coefficients.
|
||||
///
|
||||
/// # Safety
|
||||
/// This constructor validates that the coefficients are in the valid range only in debug mode.
|
||||
pub unsafe fn new(data: [u16; N]) -> Self {
|
||||
for value in data {
|
||||
debug_assert!(value < MODULUS);
|
||||
}
|
||||
|
||||
Self(data)
|
||||
}
|
||||
|
||||
/// Decodes raw bytes representing a public key into a polynomial in Z_p\[x\]/(phi).
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns an error if:
|
||||
/// - The provided input is not exactly 897 bytes long.
|
||||
/// - The first byte of the input is not equal to log2(512) i.e., 9.
|
||||
/// - Any of the coefficients encoded in the provided input is greater than or equal to the
|
||||
/// Falcon field modulus.
|
||||
pub fn from_pub_key(input: &[u8]) -> Result<Self, FalconError> {
|
||||
if input.len() != PK_LEN {
|
||||
return Err(FalconError::PubKeyDecodingInvalidLength(input.len()));
|
||||
}
|
||||
|
||||
if input[0] != LOG_N as u8 {
|
||||
return Err(FalconError::PubKeyDecodingInvalidTag(input[0]));
|
||||
}
|
||||
|
||||
let mut acc = 0_u32;
|
||||
let mut acc_len = 0;
|
||||
|
||||
let mut output = [0_u16; N];
|
||||
let mut output_idx = 0;
|
||||
|
||||
for &byte in input.iter().skip(1) {
|
||||
acc = (acc << 8) | (byte as u32);
|
||||
acc_len += 8;
|
||||
|
||||
if acc_len >= 14 {
|
||||
acc_len -= 14;
|
||||
let w = (acc >> acc_len) & 0x3FFF;
|
||||
if w >= MODULUS as u32 {
|
||||
return Err(FalconError::PubKeyDecodingInvalidCoefficient(w));
|
||||
}
|
||||
output[output_idx] = w as u16;
|
||||
output_idx += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (acc & ((1u32 << acc_len) - 1)) == 0 {
|
||||
Ok(Self(output))
|
||||
} else {
|
||||
Err(FalconError::PubKeyDecodingExtraData)
|
||||
}
|
||||
}
|
||||
|
||||
/// Decodes the signature into the coefficients of a polynomial in Z_p\[x\]/(phi). It assumes
|
||||
/// that the signature has been encoded using the uncompressed format.
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns an error if:
|
||||
/// - The signature has been encoded using a different algorithm than the reference compressed
|
||||
/// encoding algorithm.
|
||||
/// - The encoded signature polynomial is in Z_p\[x\]/(phi') where phi' = x^N' + 1 and N' != 512.
|
||||
/// - While decoding the high bits of a coefficient, the current accumulated value of its
|
||||
/// high bits is larger than 2048.
|
||||
/// - The decoded coefficient is -0.
|
||||
/// - The remaining unused bits in the last byte of `input` are non-zero.
|
||||
pub fn from_signature(input: &[u8]) -> Result<Self, FalconError> {
|
||||
let (encoding, log_n) = (input[0] >> 4, input[0] & 0b00001111);
|
||||
|
||||
if encoding != 0b0011 {
|
||||
return Err(FalconError::SigDecodingIncorrectEncodingAlgorithm);
|
||||
}
|
||||
if log_n != 0b1001 {
|
||||
return Err(FalconError::SigDecodingNotSupportedDegree(log_n));
|
||||
}
|
||||
|
||||
let input = &input[41..];
|
||||
let mut input_idx = 0;
|
||||
let mut acc = 0u32;
|
||||
let mut acc_len = 0;
|
||||
let mut output = [0_u16; N];
|
||||
|
||||
for e in output.iter_mut() {
|
||||
acc = (acc << 8) | (input[input_idx] as u32);
|
||||
input_idx += 1;
|
||||
let b = acc >> acc_len;
|
||||
let s = b & 128;
|
||||
let mut m = b & 127;
|
||||
|
||||
loop {
|
||||
if acc_len == 0 {
|
||||
acc = (acc << 8) | (input[input_idx] as u32);
|
||||
input_idx += 1;
|
||||
acc_len = 8;
|
||||
}
|
||||
acc_len -= 1;
|
||||
if ((acc >> acc_len) & 1) != 0 {
|
||||
break;
|
||||
}
|
||||
m += 128;
|
||||
if m >= 2048 {
|
||||
return Err(FalconError::SigDecodingTooBigHighBits(m));
|
||||
}
|
||||
}
|
||||
if s != 0 && m == 0 {
|
||||
return Err(FalconError::SigDecodingMinusZero);
|
||||
}
|
||||
|
||||
*e = if s != 0 { (MODULUS as u32 - m) as u16 } else { m as u16 };
|
||||
}
|
||||
|
||||
if (acc & ((1 << acc_len) - 1)) != 0 {
|
||||
return Err(FalconError::SigDecodingNonZeroUnusedBitsLastByte);
|
||||
}
|
||||
|
||||
Ok(Self(output))
|
||||
}
|
||||
|
||||
// PUBLIC ACCESSORS
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
/// Returns the coefficients of this polynomial as integers.
|
||||
pub fn inner(&self) -> [u16; N] {
|
||||
self.0
|
||||
}
|
||||
|
||||
/// Returns the coefficients of this polynomial as field elements.
|
||||
pub fn to_elements(&self) -> Vec<Felt> {
|
||||
self.0.iter().map(|&a| Felt::from(a)).collect()
|
||||
}
|
||||
|
||||
// POLYNOMIAL OPERATIONS
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
/// Multiplies two polynomials over Z_p\[x\] without reducing modulo p. Given that the degrees
|
||||
/// of the input polynomials are less than 512 and their coefficients are less than the modulus
|
||||
/// q equal to 12289, the resulting product polynomial is guaranteed to have coefficients less
|
||||
/// than the Miden prime.
|
||||
///
|
||||
/// Note that this multiplication is not over Z_p\[x\]/(phi).
|
||||
pub fn mul_modulo_p(a: &Self, b: &Self) -> [u64; 1024] {
|
||||
let mut c = [0; 2 * N];
|
||||
for i in 0..N {
|
||||
for j in 0..N {
|
||||
c[i + j] += a.0[i] as u64 * b.0[j] as u64;
|
||||
}
|
||||
}
|
||||
|
||||
c
|
||||
}
|
||||
|
||||
/// Reduces a polynomial, that is the product of two polynomials over Z_p\[x\], modulo
|
||||
/// the irreducible polynomial phi. This results in an element in Z_p\[x\]/(phi).
|
||||
pub fn reduce_negacyclic(a: &[u64; 1024]) -> Self {
|
||||
let mut c = [0; N];
|
||||
for i in 0..N {
|
||||
let ai = a[N + i] % MODULUS as u64;
|
||||
let neg_ai = (MODULUS - ai as u16) % MODULUS;
|
||||
|
||||
let bi = (a[i] % MODULUS as u64) as u16;
|
||||
c[i] = (neg_ai + bi) % MODULUS;
|
||||
}
|
||||
|
||||
Self(c)
|
||||
}
|
||||
|
||||
/// Computes the norm squared of a polynomial in Z_p\[x\]/(phi) after normalizing its
|
||||
/// coefficients to be in the interval (-p/2, p/2].
|
||||
pub fn sq_norm(&self) -> u64 {
|
||||
let mut res = 0;
|
||||
for e in self.0 {
|
||||
if e > MODULUS_MINUS_1_OVER_TWO {
|
||||
res += (MODULUS - e) as u64 * (MODULUS - e) as u64
|
||||
} else {
|
||||
res += e as u64 * e as u64
|
||||
}
|
||||
}
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
// Returns a polynomial representing the zero polynomial i.e. default element.
|
||||
impl Default for Polynomial {
|
||||
fn default() -> Self {
|
||||
Self([0_u16; N])
|
||||
}
|
||||
}
|
||||
|
||||
/// Multiplication over Z_p\[x\]/(phi)
|
||||
impl Mul for Polynomial {
|
||||
type Output = Self;
|
||||
|
||||
fn mul(self, other: Self) -> <Self as Mul<Self>>::Output {
|
||||
let mut result = [0_u16; N];
|
||||
for j in 0..N {
|
||||
for k in 0..N {
|
||||
let i = (j + k) % N;
|
||||
let a = self.0[j] as usize;
|
||||
let b = other.0[k] as usize;
|
||||
let q = MODULUS as usize;
|
||||
let mut prod = a * b % q;
|
||||
if (N - 1) < (j + k) {
|
||||
prod = (q - prod) % q;
|
||||
}
|
||||
result[i] = ((result[i] as usize + prod) % q) as u16;
|
||||
}
|
||||
}
|
||||
|
||||
Polynomial(result)
|
||||
}
|
||||
}
|
||||
|
||||
/// Addition over Z_p\[x\]/(phi)
|
||||
impl Add for Polynomial {
|
||||
type Output = Self;
|
||||
|
||||
fn add(self, other: Self) -> <Self as Add<Self>>::Output {
|
||||
let mut res = self;
|
||||
res.0.iter_mut().zip(other.0.iter()).for_each(|(x, y)| *x = (*x + *y) % MODULUS);
|
||||
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
/// Subtraction over Z_p\[x\]/(phi)
|
||||
impl Sub for Polynomial {
|
||||
type Output = Self;
|
||||
|
||||
fn sub(self, other: Self) -> <Self as Add<Self>>::Output {
|
||||
let mut res = self;
|
||||
res.0
|
||||
.iter_mut()
|
||||
.zip(other.0.iter())
|
||||
.for_each(|(x, y)| *x = (*x + MODULUS - *y) % MODULUS);
|
||||
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
// TESTS
|
||||
// ================================================================================================
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{Polynomial, N};
|
||||
|
||||
#[test]
|
||||
fn test_negacyclic_reduction() {
|
||||
let coef1: [u16; N] = rand_utils::rand_array();
|
||||
let coef2: [u16; N] = rand_utils::rand_array();
|
||||
|
||||
let poly1 = Polynomial(coef1);
|
||||
let poly2 = Polynomial(coef2);
|
||||
|
||||
assert_eq!(
|
||||
poly1 * poly2,
|
||||
Polynomial::reduce_negacyclic(&Polynomial::mul_modulo_p(&poly1, &poly2))
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,289 +1,373 @@
|
||||
use alloc::string::ToString;
|
||||
use core::cell::OnceCell;
|
||||
use alloc::{string::ToString, vec::Vec};
|
||||
use core::ops::Deref;
|
||||
|
||||
use super::{
|
||||
ByteReader, ByteWriter, Deserializable, DeserializationError, Felt, NonceBytes, NonceElements,
|
||||
Polynomial, PublicKeyBytes, Rpo256, Serializable, SignatureBytes, Word, MODULUS, N,
|
||||
SIG_L2_BOUND, ZERO,
|
||||
hash_to_point::hash_to_point_rpo256,
|
||||
keys::PubKeyPoly,
|
||||
math::{FalconFelt, FastFft, Polynomial},
|
||||
ByteReader, ByteWriter, Deserializable, DeserializationError, Felt, Nonce, Rpo256,
|
||||
Serializable, Word, LOG_N, MODULUS, N, SIG_L2_BOUND, SIG_POLY_BYTE_LEN,
|
||||
};
|
||||
use num::Zero;
|
||||
|
||||
// FALCON SIGNATURE
|
||||
// ================================================================================================
|
||||
|
||||
/// An RPO Falcon512 signature over a message.
|
||||
///
|
||||
/// The signature is a pair of polynomials (s1, s2) in (Z_p\[x\]/(phi))^2, where:
|
||||
/// The signature is a pair of polynomials (s1, s2) in (Z_p\[x\]/(phi))^2 a nonce `r`, and a public
|
||||
/// key polynomial `h` where:
|
||||
/// - p := 12289
|
||||
/// - phi := x^512 + 1
|
||||
/// - s1 = c - s2 * h
|
||||
/// - h is a polynomial representing the public key and c is a polynomial that is the hash-to-point
|
||||
/// of the message being signed.
|
||||
///
|
||||
/// The signature verifies if and only if:
|
||||
/// The signature verifies against a public key `pk` if and only if:
|
||||
/// 1. s1 = c - s2 * h
|
||||
/// 2. |s1|^2 + |s2|^2 <= SIG_L2_BOUND
|
||||
///
|
||||
/// where |.| is the norm.
|
||||
/// where |.| is the norm and:
|
||||
/// - c = HashToPoint(r || message)
|
||||
/// - pk = Rpo256::hash(h)
|
||||
///
|
||||
/// [Signature] also includes the extended public key which is serialized as:
|
||||
/// Here h is a polynomial representing the public key and pk is its digest using the Rpo256 hash
|
||||
/// function. c is a polynomial that is the hash-to-point of the message being signed.
|
||||
///
|
||||
/// The polynomial h is serialized as:
|
||||
/// 1. 1 byte representing the log2(512) i.e., 9.
|
||||
/// 2. 896 bytes for the public key. This is decoded into the `h` polynomial above.
|
||||
/// 2. 896 bytes for the public key itself.
|
||||
///
|
||||
/// The actual signature is serialized as:
|
||||
/// The signature is serialized as:
|
||||
/// 1. A header byte specifying the algorithm used to encode the coefficients of the `s2` polynomial
|
||||
/// together with the degree of the irreducible polynomial phi.
|
||||
/// The general format of this byte is 0b0cc1nnnn where:
|
||||
/// a. cc is either 01 when the compressed encoding algorithm is used and 10 when the
|
||||
/// uncompressed algorithm is used.
|
||||
/// b. nnnn is log2(N) where N is the degree of the irreducible polynomial phi.
|
||||
/// The current implementation works always with cc equal to 0b01 and nnnn equal to 0b1001 and
|
||||
/// thus the header byte is always equal to 0b00111001.
|
||||
/// together with the degree of the irreducible polynomial phi. For RPO Falcon512, the header
|
||||
/// byte is set to `10111001` which differentiates it from the standardized instantiation of
|
||||
/// the Falcon signature.
|
||||
/// 2. 40 bytes for the nonce.
|
||||
/// 3. 625 bytes encoding the `s2` polynomial above.
|
||||
/// 4. 625 bytes encoding the `s2` polynomial above.
|
||||
///
|
||||
/// The total size of the signature (including the extended public key) is 1563 bytes.
|
||||
#[derive(Debug, Clone)]
|
||||
/// The total size of the signature is (including the extended public key) is 1563 bytes.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Signature {
|
||||
pub(super) pk: PublicKeyBytes,
|
||||
pub(super) sig: SignatureBytes,
|
||||
|
||||
// Cached polynomial decoding for public key and signatures
|
||||
pub(super) pk_polynomial: OnceCell<Polynomial>,
|
||||
pub(super) sig_polynomial: OnceCell<Polynomial>,
|
||||
header: SignatureHeader,
|
||||
nonce: Nonce,
|
||||
s2: SignaturePoly,
|
||||
h: PubKeyPoly,
|
||||
}
|
||||
|
||||
impl Signature {
|
||||
// CONSTRUCTOR
|
||||
// --------------------------------------------------------------------------------------------
|
||||
pub fn new(nonce: Nonce, h: PubKeyPoly, s2: SignaturePoly) -> Signature {
|
||||
Self {
|
||||
header: SignatureHeader::default(),
|
||||
nonce,
|
||||
s2,
|
||||
h,
|
||||
}
|
||||
}
|
||||
|
||||
// PUBLIC ACCESSORS
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
/// Returns the public key polynomial h.
|
||||
pub fn pub_key_poly(&self) -> Polynomial {
|
||||
*self.pk_polynomial.get_or_init(|| {
|
||||
// we assume that the signature was constructed with a valid public key, and thus
|
||||
// expect() is OK here.
|
||||
Polynomial::from_pub_key(&self.pk).expect("invalid public key")
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the nonce component of the signature represented as field elements.
|
||||
///
|
||||
/// Nonce bytes are converted to field elements by taking consecutive 5 byte chunks
|
||||
/// of the nonce and interpreting them as field elements.
|
||||
pub fn nonce(&self) -> NonceElements {
|
||||
// we assume that the signature was constructed with a valid signature, and thus
|
||||
// expect() is OK here.
|
||||
let nonce = self.sig[1..41].try_into().expect("invalid signature");
|
||||
decode_nonce(nonce)
|
||||
pub fn pk_poly(&self) -> &PubKeyPoly {
|
||||
&self.h
|
||||
}
|
||||
|
||||
// Returns the polynomial representation of the signature in Z_p[x]/(phi).
|
||||
pub fn sig_poly(&self) -> Polynomial {
|
||||
*self.sig_polynomial.get_or_init(|| {
|
||||
// we assume that the signature was constructed with a valid signature, and thus
|
||||
// expect() is OK here.
|
||||
Polynomial::from_signature(&self.sig).expect("invalid signature")
|
||||
})
|
||||
pub fn sig_poly(&self) -> &Polynomial<FalconFelt> {
|
||||
&self.s2
|
||||
}
|
||||
|
||||
// HASH-TO-POINT
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
/// Returns a polynomial in Z_p\[x\]/(phi) representing the hash of the provided message.
|
||||
pub fn hash_to_point(&self, message: Word) -> Polynomial {
|
||||
hash_to_point(message, &self.nonce())
|
||||
/// Returns the nonce component of the signature.
|
||||
pub fn nonce(&self) -> &Nonce {
|
||||
&self.nonce
|
||||
}
|
||||
|
||||
// SIGNATURE VERIFICATION
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
/// Returns true if this signature is a valid signature for the specified message generated
|
||||
/// against key pair matching the specified public key commitment.
|
||||
/// against the secret key matching the specified public key commitment.
|
||||
pub fn verify(&self, message: Word, pubkey_com: Word) -> bool {
|
||||
// Make sure the expanded public key matches the provided public key commitment
|
||||
let h = self.pub_key_poly();
|
||||
let h_digest: Word = Rpo256::hash_elements(&h.to_elements()).into();
|
||||
// compute the hash of the public key polynomial
|
||||
let h_felt: Polynomial<Felt> = (&**self.pk_poly()).into();
|
||||
let h_digest: Word = Rpo256::hash_elements(&h_felt.coefficients).into();
|
||||
if h_digest != pubkey_com {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Make sure the signature is valid
|
||||
let s2 = self.sig_poly();
|
||||
let c = self.hash_to_point(message);
|
||||
|
||||
let s1 = c - s2 * h;
|
||||
|
||||
let sq_norm = s1.sq_norm() + s2.sq_norm();
|
||||
sq_norm <= SIG_L2_BOUND
|
||||
let c = hash_to_point_rpo256(message, &self.nonce);
|
||||
h_digest == pubkey_com && verify_helper(&c, &self.s2, self.pk_poly())
|
||||
}
|
||||
}
|
||||
|
||||
// SERIALIZATION / DESERIALIZATION
|
||||
// ================================================================================================
|
||||
|
||||
impl Serializable for Signature {
|
||||
fn write_into<W: ByteWriter>(&self, target: &mut W) {
|
||||
target.write_bytes(&self.pk);
|
||||
target.write_bytes(&self.sig);
|
||||
target.write(&self.header);
|
||||
target.write(&self.nonce);
|
||||
target.write(&self.s2);
|
||||
target.write(&self.h);
|
||||
}
|
||||
}
|
||||
|
||||
impl Deserializable for Signature {
|
||||
fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
|
||||
let pk: PublicKeyBytes = source.read_array()?;
|
||||
let sig: SignatureBytes = source.read_array()?;
|
||||
let header = source.read()?;
|
||||
let nonce = source.read()?;
|
||||
let s2 = source.read()?;
|
||||
let h = source.read()?;
|
||||
|
||||
// make sure public key and signature can be decoded correctly
|
||||
let pk_polynomial = Polynomial::from_pub_key(&pk)
|
||||
.map_err(|err| DeserializationError::InvalidValue(err.to_string()))?
|
||||
.into();
|
||||
let sig_polynomial = Polynomial::from_signature(&sig)
|
||||
.map_err(|err| DeserializationError::InvalidValue(err.to_string()))?
|
||||
.into();
|
||||
Ok(Self { header, nonce, s2, h })
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Self { pk, sig, pk_polynomial, sig_polynomial })
|
||||
// SIGNATURE HEADER
|
||||
// ================================================================================================
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct SignatureHeader(u8);
|
||||
|
||||
impl Default for SignatureHeader {
|
||||
/// According to section 3.11.3 in the specification [1], the signature header has the format
|
||||
/// `0cc1nnnn` where:
|
||||
///
|
||||
/// 1. `cc` signifies the encoding method. `01` denotes using the compression encoding method
|
||||
/// and `10` denotes encoding using the uncompressed method.
|
||||
/// 2. `nnnn` encodes `LOG_N`.
|
||||
///
|
||||
/// For RPO Falcon 512 we use compression encoding and N = 512. Moreover, to differentiate the
|
||||
/// RPO Falcon variant from the reference variant using SHAKE256, we flip the first bit in the
|
||||
/// header. Thus, for RPO Falcon 512 the header is `10111001`
|
||||
///
|
||||
/// [1]: https://falcon-sign.info/falcon.pdf
|
||||
fn default() -> Self {
|
||||
Self(0b1011_1001)
|
||||
}
|
||||
}
|
||||
|
||||
impl Serializable for &SignatureHeader {
|
||||
fn write_into<W: ByteWriter>(&self, target: &mut W) {
|
||||
target.write_u8(self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Deserializable for SignatureHeader {
|
||||
fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
|
||||
let header = source.read_u8()?;
|
||||
let (encoding, log_n) = (header >> 4, header & 0b00001111);
|
||||
if encoding != 0b1011 {
|
||||
return Err(DeserializationError::InvalidValue(
|
||||
"Failed to decode signature: not supported encoding algorithm".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
if log_n != LOG_N {
|
||||
return Err(DeserializationError::InvalidValue(
|
||||
format!("Failed to decode signature: only supported irreducible polynomial degree is 512, 2^{log_n} was provided")
|
||||
));
|
||||
}
|
||||
|
||||
Ok(Self(header))
|
||||
}
|
||||
}
|
||||
|
||||
// SIGNATURE POLYNOMIAL
|
||||
// ================================================================================================
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct SignaturePoly(pub Polynomial<FalconFelt>);
|
||||
|
||||
impl Deref for SignaturePoly {
|
||||
type Target = Polynomial<FalconFelt>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Polynomial<FalconFelt>> for SignaturePoly {
|
||||
fn from(pk_poly: Polynomial<FalconFelt>) -> Self {
|
||||
Self(pk_poly)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&[i16; N]> for SignaturePoly {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(coefficients: &[i16; N]) -> Result<Self, Self::Error> {
|
||||
if are_coefficients_valid(coefficients) {
|
||||
Ok(Self(coefficients.to_vec().into()))
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Serializable for &SignaturePoly {
|
||||
fn write_into<W: ByteWriter>(&self, target: &mut W) {
|
||||
let sig_coeff: Vec<i16> = self.0.coefficients.iter().map(|a| a.balanced_value()).collect();
|
||||
let mut sk_bytes = vec![0_u8; SIG_POLY_BYTE_LEN];
|
||||
|
||||
let mut acc = 0;
|
||||
let mut acc_len = 0;
|
||||
let mut v = 0;
|
||||
let mut t;
|
||||
let mut w;
|
||||
|
||||
// For each coefficient of x:
|
||||
// - the sign is encoded on 1 bit
|
||||
// - the 7 lower bits are encoded naively (binary)
|
||||
// - the high bits are encoded in unary encoding
|
||||
//
|
||||
// Algorithm 17 p. 47 of the specification [1].
|
||||
//
|
||||
// [1]: https://falcon-sign.info/falcon.pdf
|
||||
for &c in sig_coeff.iter() {
|
||||
acc <<= 1;
|
||||
t = c;
|
||||
|
||||
if t < 0 {
|
||||
t = -t;
|
||||
acc |= 1;
|
||||
}
|
||||
w = t as u16;
|
||||
|
||||
acc <<= 7;
|
||||
let mask = 127_u32;
|
||||
acc |= (w as u32) & mask;
|
||||
w >>= 7;
|
||||
|
||||
acc_len += 8;
|
||||
|
||||
acc <<= w + 1;
|
||||
acc |= 1;
|
||||
acc_len += w + 1;
|
||||
|
||||
while acc_len >= 8 {
|
||||
acc_len -= 8;
|
||||
|
||||
sk_bytes[v] = (acc >> acc_len) as u8;
|
||||
v += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if acc_len > 0 {
|
||||
sk_bytes[v] = (acc << (8 - acc_len)) as u8;
|
||||
}
|
||||
target.write_bytes(&sk_bytes);
|
||||
}
|
||||
}
|
||||
|
||||
impl Deserializable for SignaturePoly {
|
||||
fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
|
||||
let input = source.read_array::<SIG_POLY_BYTE_LEN>()?;
|
||||
|
||||
let mut input_idx = 0;
|
||||
let mut acc = 0u32;
|
||||
let mut acc_len = 0;
|
||||
let mut coefficients = [FalconFelt::zero(); N];
|
||||
|
||||
// Algorithm 18 p. 48 of the specification [1].
|
||||
//
|
||||
// [1]: https://falcon-sign.info/falcon.pdf
|
||||
for c in coefficients.iter_mut() {
|
||||
acc = (acc << 8) | (input[input_idx] as u32);
|
||||
input_idx += 1;
|
||||
let b = acc >> acc_len;
|
||||
let s = b & 128;
|
||||
let mut m = b & 127;
|
||||
|
||||
loop {
|
||||
if acc_len == 0 {
|
||||
acc = (acc << 8) | (input[input_idx] as u32);
|
||||
input_idx += 1;
|
||||
acc_len = 8;
|
||||
}
|
||||
acc_len -= 1;
|
||||
if ((acc >> acc_len) & 1) != 0 {
|
||||
break;
|
||||
}
|
||||
m += 128;
|
||||
if m >= 2048 {
|
||||
return Err(DeserializationError::InvalidValue(
|
||||
"Failed to decode signature: high bits {m} exceed 2048".to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
if s != 0 && m == 0 {
|
||||
return Err(DeserializationError::InvalidValue(
|
||||
"Failed to decode signature: -0 is forbidden".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let felt = if s != 0 { (MODULUS as u32 - m) as u16 } else { m as u16 };
|
||||
*c = FalconFelt::new(felt as i16);
|
||||
}
|
||||
|
||||
if (acc & ((1 << acc_len) - 1)) != 0 {
|
||||
return Err(DeserializationError::InvalidValue(
|
||||
"Failed to decode signature: Non-zero unused bits in the last byte".to_string(),
|
||||
));
|
||||
}
|
||||
Ok(Polynomial::new(coefficients.to_vec()).into())
|
||||
}
|
||||
}
|
||||
|
||||
// HELPER FUNCTIONS
|
||||
// ================================================================================================
|
||||
|
||||
/// Returns a polynomial in Z_p[x]/(phi) representing the hash of the provided message and
|
||||
/// nonce.
|
||||
fn hash_to_point(message: Word, nonce: &NonceElements) -> Polynomial {
|
||||
let mut state = [ZERO; Rpo256::STATE_WIDTH];
|
||||
/// Takes the hash-to-point polynomial `c` of a message, the signature polynomial over
|
||||
/// the message `s2` and a public key polynomial and returns `true` is the signature is a valid
|
||||
/// signature for the given parameters, otherwise it returns `false`.
|
||||
fn verify_helper(c: &Polynomial<FalconFelt>, s2: &SignaturePoly, h: &PubKeyPoly) -> bool {
|
||||
let h_fft = h.fft();
|
||||
let s2_fft = s2.fft();
|
||||
let c_fft = c.fft();
|
||||
|
||||
// absorb the nonce into the state
|
||||
for (&n, s) in nonce.iter().zip(state[Rpo256::RATE_RANGE].iter_mut()) {
|
||||
*s = n;
|
||||
}
|
||||
Rpo256::apply_permutation(&mut state);
|
||||
// compute the signature polynomial s1 using s1 = c - s2 * h
|
||||
let s1_fft = c_fft - s2_fft.hadamard_mul(&h_fft);
|
||||
let s1 = s1_fft.ifft();
|
||||
|
||||
// absorb message into the state
|
||||
for (&m, s) in message.iter().zip(state[Rpo256::RATE_RANGE].iter_mut()) {
|
||||
*s = m;
|
||||
// compute the norm squared of (s1, s2)
|
||||
let length_squared_s1 = s1.norm_squared();
|
||||
let length_squared_s2 = s2.norm_squared();
|
||||
let length_squared = length_squared_s1 + length_squared_s2;
|
||||
|
||||
length_squared < SIG_L2_BOUND
|
||||
}
|
||||
|
||||
/// Checks whether a set of coefficients is a valid one for a signature polynomial.
|
||||
fn are_coefficients_valid(x: &[i16]) -> bool {
|
||||
if x.len() != N {
|
||||
return false;
|
||||
}
|
||||
|
||||
// squeeze the coefficients of the polynomial
|
||||
let mut i = 0;
|
||||
let mut res = [0_u16; N];
|
||||
for _ in 0..64 {
|
||||
Rpo256::apply_permutation(&mut state);
|
||||
for a in &state[Rpo256::RATE_RANGE] {
|
||||
res[i] = (a.as_int() % MODULUS as u64) as u16;
|
||||
i += 1;
|
||||
for &c in x {
|
||||
if !(-2047..=2047).contains(&c) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// using the raw constructor is OK here because we reduce all coefficients by the modulus above
|
||||
unsafe { Polynomial::new(res) }
|
||||
}
|
||||
|
||||
/// Converts byte representation of the nonce into field element representation.
|
||||
fn decode_nonce(nonce: &NonceBytes) -> NonceElements {
|
||||
let mut buffer = [0_u8; 8];
|
||||
let mut result = [ZERO; 8];
|
||||
for (i, bytes) in nonce.chunks(5).enumerate() {
|
||||
buffer[..5].copy_from_slice(bytes);
|
||||
// we can safely (without overflow) create a new Felt from u64 value here since this value
|
||||
// contains at most 5 bytes
|
||||
result[i] = Felt::new(u64::from_le_bytes(buffer));
|
||||
}
|
||||
|
||||
result
|
||||
true
|
||||
}
|
||||
|
||||
// TESTS
|
||||
// ================================================================================================
|
||||
|
||||
#[cfg(all(test, feature = "std"))]
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use core::ffi::c_void;
|
||||
use rand_utils::rand_vector;
|
||||
|
||||
use super::{
|
||||
super::{ffi::*, KeyPair},
|
||||
*,
|
||||
};
|
||||
|
||||
// Wrappers for unsafe functions
|
||||
impl Rpo128Context {
|
||||
/// Initializes the RPO state.
|
||||
pub fn init() -> Self {
|
||||
let mut ctx = Rpo128Context { content: [0u64; 13] };
|
||||
unsafe {
|
||||
rpo128_init(&mut ctx as *mut Rpo128Context);
|
||||
}
|
||||
ctx
|
||||
}
|
||||
|
||||
/// Absorbs data into the RPO state.
|
||||
pub fn absorb(&mut self, data: &[u8]) {
|
||||
unsafe {
|
||||
rpo128_absorb(
|
||||
self as *mut Rpo128Context,
|
||||
data.as_ptr() as *const c_void,
|
||||
data.len(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Finalizes the RPO state to prepare for squeezing.
|
||||
pub fn finalize(&mut self) {
|
||||
unsafe { rpo128_finalize(self as *mut Rpo128Context) }
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hash_to_point() {
|
||||
// Create a random message and transform it into a u8 vector
|
||||
let msg_felts: Word = rand_vector::<Felt>(4).try_into().unwrap();
|
||||
let msg_bytes = msg_felts
|
||||
.iter()
|
||||
.flat_map(|e| e.as_int().to_le_bytes())
|
||||
.collect::<alloc::vec::Vec<_>>();
|
||||
|
||||
// Create a nonce i.e. a [u8; 40] array and pack into a [Felt; 8] array.
|
||||
let nonce: [u8; 40] = rand_vector::<u8>(40).try_into().unwrap();
|
||||
|
||||
let mut buffer = [0_u8; 64];
|
||||
for i in 0..8 {
|
||||
buffer[8 * i] = nonce[5 * i];
|
||||
buffer[8 * i + 1] = nonce[5 * i + 1];
|
||||
buffer[8 * i + 2] = nonce[5 * i + 2];
|
||||
buffer[8 * i + 3] = nonce[5 * i + 3];
|
||||
buffer[8 * i + 4] = nonce[5 * i + 4];
|
||||
}
|
||||
|
||||
// Initialize the RPO state
|
||||
let mut rng = Rpo128Context::init();
|
||||
|
||||
// Absorb the nonce and message into the RPO state
|
||||
rng.absorb(&buffer);
|
||||
rng.absorb(&msg_bytes);
|
||||
rng.finalize();
|
||||
|
||||
// Generate the coefficients of the hash-to-point polynomial.
|
||||
let mut res: [u16; N] = [0; N];
|
||||
|
||||
unsafe {
|
||||
PQCLEAN_FALCON512_CLEAN_hash_to_point_rpo(
|
||||
&mut rng as *mut Rpo128Context,
|
||||
res.as_mut_ptr(),
|
||||
9,
|
||||
);
|
||||
}
|
||||
|
||||
// Check that the coefficients are correct
|
||||
let nonce = decode_nonce(&nonce);
|
||||
assert_eq!(res, hash_to_point(msg_felts, &nonce).inner());
|
||||
}
|
||||
use super::{super::SecretKey, *};
|
||||
use rand::SeedableRng;
|
||||
use rand_chacha::ChaCha20Rng;
|
||||
|
||||
#[test]
|
||||
fn test_serialization_round_trip() {
|
||||
let key = KeyPair::new().unwrap();
|
||||
let signature = key.sign(Word::default()).unwrap();
|
||||
let seed = [0_u8; 32];
|
||||
let mut rng = ChaCha20Rng::from_seed(seed);
|
||||
|
||||
let sk = SecretKey::with_rng(&mut rng);
|
||||
let signature = sk.sign_with_rng(Word::default(), &mut rng);
|
||||
let serialized = signature.to_bytes();
|
||||
let deserialized = Signature::read_from_bytes(&serialized).unwrap();
|
||||
assert_eq!(signature.sig_poly(), deserialized.sig_poly());
|
||||
assert_eq!(signature.pub_key_poly(), deserialized.pub_key_poly());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user