1221 lines
40 KiB
Rust
1221 lines
40 KiB
Rust
// TODO(dianamin): Try moving Polys on the heap using boxing.
|
|
// TODO(dianamin): Remove ": Poly" where it is not necessary.
|
|
// TODO(dianamin): Add a function that replaces let x: Poly = [0; N]
|
|
|
|
use packing;
|
|
use params::{
|
|
BETA, CRHBYTES, GAMMA1, GAMMA2, K, L, N, OMEGA, PK_SIZE_PACKED, POLETA_SIZE_PACKED,
|
|
POLT0_SIZE_PACKED, POLT1_SIZE_PACKED, POLW1_SIZE_PACKED, SEEDBYTES, SIG_SIZE_PACKED,
|
|
SK_SIZE_PACKED, SK_SIZE_PACKED_ORIGINAL,
|
|
};
|
|
use poly::{self, Poly};
|
|
use polyvec::{self, PolyVecK, PolyVecL};
|
|
|
|
use digest::{ExtendableOutput, Input, XofReader};
|
|
use sha3::Shake256;
|
|
|
|
/// Helper function used both when signing and verifying.
|
|
///
|
|
/// Expands `A[i][j]` from the randomness seed `rho`.
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `rho` - an array of random bytes
|
|
/// * `i` - the index of the row
|
|
/// * `j` - the index of the column
|
|
/// * `mat_component` - the output polynomial representing `A[i][j]`
|
|
fn expand_mat_component(rho: &[u8; SEEDBYTES], i: usize, j: usize, mat_component: &mut Poly) {
|
|
poly::uniform(mat_component, rho, ((i << 8) + j) as u16);
|
|
}
|
|
|
|
/// Helper function used when signing in optimized speed mode.
|
|
///
|
|
/// Expands the matrix `A` from the randomness seed `rho`.
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `rho` - an array of random bytes
|
|
/// * `mat` - a matrix of polynomials of `K` rows and `L` columns.
|
|
#[cfg(not(feature = "optimize_stack"))]
|
|
fn expand_mat(rho: &[u8; SEEDBYTES], mat: &mut [PolyVecL; K]) {
|
|
for i in 0..K {
|
|
for j in 0..L {
|
|
expand_mat_component(rho, i, j, &mut mat[i][j]);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
pub struct SecKey {
|
|
rho: [u8; SEEDBYTES],
|
|
key: [u8; SEEDBYTES],
|
|
tr: [u8; SEEDBYTES],
|
|
s1_packed: [[u8; POLETA_SIZE_PACKED]; L],
|
|
s2_packed: [[u8; POLETA_SIZE_PACKED]; K],
|
|
}
|
|
|
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
pub struct PubKey {
|
|
pub rho: [u8; SEEDBYTES],
|
|
pub t1_packed: [[u8; POLT1_SIZE_PACKED]; K],
|
|
}
|
|
|
|
impl Default for SecKey {
|
|
fn default() -> SecKey {
|
|
SecKey {
|
|
rho: [0; SEEDBYTES],
|
|
key: [0; SEEDBYTES],
|
|
tr: [0; SEEDBYTES],
|
|
s1_packed: [[0; POLETA_SIZE_PACKED]; L],
|
|
s2_packed: [[0; POLETA_SIZE_PACKED]; K],
|
|
}
|
|
}
|
|
}
|
|
|
|
impl SecKey {
|
|
/// Encodes the secret key to an array of bytes.
|
|
///
|
|
/// Fills the bytes array as follows:
|
|
/// `rho || key || tr || encodings of s1 || encodings of s2`
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `bytes` - an array of bytes where the encoding will be stored.
|
|
pub fn to_bytes(&self, bytes: &mut [u8; SK_SIZE_PACKED]) {
|
|
let mut offset = 0;
|
|
let mut push = |data: &[u8]| {
|
|
bytes[offset..][..data.len()].copy_from_slice(data);
|
|
offset += data.len();
|
|
};
|
|
|
|
push(&self.rho);
|
|
push(&self.key);
|
|
push(&self.tr);
|
|
for i in 0..L {
|
|
push(&self.s1_packed[i]);
|
|
}
|
|
for i in 0..K {
|
|
push(&self.s2_packed[i]);
|
|
}
|
|
}
|
|
|
|
/// Encodes the secret key to an array of bytes that includes `t0`.
|
|
///
|
|
/// Fills the bytes array as follows:
|
|
/// `rho || key || tr || encodings of s1 || encodings of s2 ||
|
|
/// encodings of t0`
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `bytes` - an array of bytes where the encoding will be stored.
|
|
pub fn to_bytes_original(&self, bytes: &mut [u8; SK_SIZE_PACKED_ORIGINAL]) {
|
|
self.to_bytes(array_mut_ref!(bytes, 0, SK_SIZE_PACKED));
|
|
|
|
let mut offset = SK_SIZE_PACKED;
|
|
let mut push = |data: &[u8]| {
|
|
bytes[offset..][..data.len()].copy_from_slice(data);
|
|
offset += data.len();
|
|
};
|
|
|
|
#[cfg(feature = "optimize_stack")]
|
|
{
|
|
for i in 0..K {
|
|
let t0_component = poly::power2round_remainder(&self.compute_t_component(i));
|
|
|
|
let mut t0_bytes = [0u8; POLT0_SIZE_PACKED];
|
|
poly::t0_pack(&mut t0_bytes, &t0_component);
|
|
push(&t0_bytes);
|
|
}
|
|
}
|
|
#[cfg(not(feature = "optimize_stack"))]
|
|
{
|
|
let mut mat = [PolyVecL::default(); K];
|
|
expand_mat(&self.rho, &mut mat);
|
|
let mut s1 = self.compute_s1();
|
|
s1.ntt();
|
|
let s2 = self.compute_s2();
|
|
|
|
for i in 0..K {
|
|
let t0_component =
|
|
poly::power2round_remainder(&self.compute_t_component(&mat, &s1, &s2, i));
|
|
|
|
let mut t0_bytes = [0u8; POLT0_SIZE_PACKED];
|
|
poly::t0_pack(&mut t0_bytes, &t0_component);
|
|
push(&t0_bytes);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Decodes the secret key from an array of bytes.
|
|
///
|
|
/// Extracts the fields from an array with the following shape:
|
|
/// `rho || key || tr || encodings of s1 || encodings of s2`
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `bytes` - an array of byres representing the secret key's encoding.
|
|
pub fn from_bytes(bytes: &[u8; SK_SIZE_PACKED]) -> SecKey {
|
|
let mut offset = 0;
|
|
let mut pull = |data: &mut [u8]| {
|
|
data.copy_from_slice(&bytes[offset..][..data.len()]);
|
|
offset += data.len();
|
|
};
|
|
|
|
let mut sk = SecKey::default();
|
|
|
|
pull(&mut sk.rho);
|
|
pull(&mut sk.key);
|
|
pull(&mut sk.tr);
|
|
|
|
for i in 0..L {
|
|
pull(&mut sk.s1_packed[i]);
|
|
}
|
|
for i in 0..K {
|
|
pull(&mut sk.s2_packed[i]);
|
|
}
|
|
|
|
sk
|
|
}
|
|
|
|
/// Decodes the secret key from an array of bytes that includes `t0`.
|
|
///
|
|
/// Extracts the fields from an array with the following shape:
|
|
/// `rho || key || tr || encodings of s1 || encodings of s2 || encodings of t0`,
|
|
/// which is the original shape of the secret key in Dilithium.
|
|
/// In our implementation, the encodings of `t0` are removed.
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `bytes` - an array of byres representing the secret key's encoding.
|
|
pub fn from_bytes_original(bytes: &[u8; SK_SIZE_PACKED_ORIGINAL]) -> SecKey {
|
|
Self::from_bytes(array_ref!(&bytes, 0, SK_SIZE_PACKED))
|
|
}
|
|
|
|
/// Returns the public key.
|
|
pub fn genpk(&self) -> PubKey {
|
|
let mut pk = PubKey::default();
|
|
pk.rho = self.rho.clone();
|
|
|
|
// Compute t = A * s1 + s2
|
|
// And extract t1: the quotient of t / 2^D
|
|
|
|
#[cfg(feature = "optimize_stack")]
|
|
{
|
|
for i in 0..K {
|
|
let t1_component = poly::power2round_quotient(&self.compute_t_component(i));
|
|
poly::t1_pack(&mut pk.t1_packed[i], &t1_component);
|
|
}
|
|
}
|
|
|
|
#[cfg(not(feature = "optimize_stack"))]
|
|
{
|
|
let mut mat = [PolyVecL::default(); K];
|
|
expand_mat(&self.rho, &mut mat);
|
|
|
|
let mut s1 = self.compute_s1();
|
|
s1.ntt();
|
|
let s2 = self.compute_s2();
|
|
for i in 0..K {
|
|
let t1_component =
|
|
poly::power2round_quotient(&self.compute_t_component(&mat, &s1, &s2, i));
|
|
poly::t1_pack(&mut pk.t1_packed[i], &t1_component);
|
|
}
|
|
}
|
|
|
|
pk
|
|
}
|
|
|
|
/// Generates a new secret key.
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `rng` - random number generator.
|
|
pub fn gensk(rng: &mut impl rng256::Rng256) -> Self {
|
|
let (sk, _) = Self::gensk_with_pk(rng);
|
|
sk
|
|
}
|
|
|
|
/// Generates a new secret key and a new public key.
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `rng` - random number generator.
|
|
pub fn gensk_with_pk(rng: &mut impl rng256::Rng256) -> (Self, PubKey) {
|
|
let mut seed = [0u8; SEEDBYTES];
|
|
rng.fill_bytes(&mut seed);
|
|
Self::gensk_with_pk_from_seed(&seed)
|
|
}
|
|
|
|
/// Generates a new secret key from a given random seed.
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `seedbuff` - a random seed.
|
|
pub fn gensk_from_seed(seed: &[u8; SEEDBYTES]) -> Self {
|
|
let (sk, _) = Self::gensk_with_pk_from_seed(&seed);
|
|
sk
|
|
}
|
|
|
|
/// Generates a new secret key and public key from a given random seed.
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `seedbuff` - a random seed.
|
|
pub fn gensk_with_pk_from_seed(seed: &[u8; SEEDBYTES]) -> (Self, PubKey) {
|
|
let mut sk = SecKey::default();
|
|
let mut pk = PubKey::default();
|
|
|
|
// Expand 32 bytes of randomness into rho, rhoprime and key.
|
|
let mut seedbuf = [0u8; 2 * SEEDBYTES + CRHBYTES];
|
|
seedbuf[..SEEDBYTES].copy_from_slice(seed);
|
|
shake256!(&mut seedbuf; &seedbuf[..SEEDBYTES]);
|
|
sk.rho.clone_from(array_ref!(seedbuf, 0, SEEDBYTES));
|
|
sk.key
|
|
.clone_from(array_ref!(seedbuf, SEEDBYTES + CRHBYTES, SEEDBYTES));
|
|
let rhoprime = array_ref!(seedbuf, SEEDBYTES, CRHBYTES);
|
|
let mut nonce = 0;
|
|
|
|
pk.rho = sk.rho;
|
|
|
|
// In the optimized stack mode, we store as few variables as possible.
|
|
// Because of that, some of the variables will be recomputed.
|
|
#[cfg(feature = "optimize_stack")]
|
|
{
|
|
// Expand the short vector s1 from rhoprime and store the encoding
|
|
// of each s1[i] in the secret key.
|
|
for i in 0..L {
|
|
let mut s1_component: Poly = [0; N];
|
|
poly::uniform_eta(&mut s1_component, rhoprime, nonce);
|
|
poly::eta_pack(&mut sk.s1_packed[i], &s1_component);
|
|
nonce += 1;
|
|
}
|
|
|
|
// Expand the short vector s2 from rhoprime and store the encoding
|
|
// of each s2[i] in the secret key.
|
|
for i in 0..K {
|
|
let mut s2_component: Poly = [0; N];
|
|
poly::uniform_eta(&mut s2_component, rhoprime, nonce);
|
|
poly::eta_pack(&mut sk.s2_packed[i], &s2_component);
|
|
nonce += 1;
|
|
}
|
|
|
|
// Computes t1: the quotient of t (= A * s1 + s2) / 2^D.
|
|
for i in 0..K {
|
|
let t1_component = poly::power2round_quotient(&sk.compute_t_component(i));
|
|
poly::t1_pack(&mut pk.t1_packed[i], &t1_component);
|
|
}
|
|
}
|
|
|
|
// In the optimized speed mode, the variables are computed and stored.
|
|
#[cfg(not(feature = "optimize_stack"))]
|
|
{
|
|
// Expand the short vector s1 from rhoprime and store the encoding
|
|
// of each s1[i] in the secret key.
|
|
let mut s1 = PolyVecL::default();
|
|
for i in 0..L {
|
|
poly::uniform_eta(&mut s1[i], rhoprime, nonce);
|
|
poly::eta_pack(&mut sk.s1_packed[i], &s1[i]);
|
|
nonce += 1;
|
|
}
|
|
|
|
// Expand the short vector s2 from rhoprime and store the encoding
|
|
// of each s2[i] in the secret key.
|
|
let mut s2 = PolyVecK::default();
|
|
for i in 0..K {
|
|
poly::uniform_eta(&mut s2[i], rhoprime, nonce);
|
|
poly::eta_pack(&mut sk.s2_packed[i], &s2[i]);
|
|
nonce += 1;
|
|
}
|
|
|
|
// Computes t1: the quotient of t (= A * s1 + s2) / 2^D.
|
|
let mut mat = [PolyVecL::default(); K];
|
|
expand_mat(&sk.rho, &mut mat);
|
|
s1.ntt();
|
|
for i in 0..K {
|
|
let t1_component =
|
|
poly::power2round_quotient(&sk.compute_t_component(&mat, &s1, &s2, i));
|
|
poly::t1_pack(&mut pk.t1_packed[i], &t1_component);
|
|
}
|
|
}
|
|
|
|
// Compute tr = CRH(rho || encodings of t1)
|
|
let mut hasher = Shake256::default();
|
|
hasher.process(&sk.rho);
|
|
for i in 0..K {
|
|
hasher.process(&pk.t1_packed[i]);
|
|
}
|
|
|
|
let mut xof = hasher.xof_result();
|
|
xof.read(&mut sk.tr);
|
|
|
|
(sk, pk)
|
|
}
|
|
|
|
/// Returns a random polynomial `y[i]`.
|
|
///
|
|
/// Computes the `i`-th component of `y`, where:
|
|
/// - `y` is a vector of polynomials 'sampled' when signing using
|
|
/// `rhoprime` (based on`key`, `mu`, `nonce`)
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `rhoprime` - an array of bytes obrained as `SHA256(key || mu)`
|
|
/// * `nonce` - current count of the attempts to sign the given message
|
|
/// * `i` - the index of the `y` component to be computed
|
|
fn compute_y_component(&self, rhoprime: &[u8; CRHBYTES], nonce: u16, i: u16) -> Poly {
|
|
let mut y_component: Poly = [0; N];
|
|
// y[i]: poly_uniform_gamma1(&y->vec[i], seed, L*nonce + i);
|
|
let nonce = (L as u16 * nonce + i) as u16;
|
|
poly::uniform_gamma1m1(&mut y_component, rhoprime, nonce);
|
|
y_component
|
|
}
|
|
|
|
/// Returns a random vector of `L` polynomials `y`.
|
|
///
|
|
/// Computes the polynomial of vectors `y`, where:
|
|
/// - `y` is a vector of polynomials 'sampled' when signing using
|
|
/// `rhoprime` (based on`key`, `mu`, `nonce`)
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `rhoprime` - an array of bytes obrained as `SHA256(key || mu)`
|
|
/// * `nonce` - current count of the attempts to sign the given message
|
|
#[cfg(not(feature = "optimize_stack"))]
|
|
fn compute_y(&self, rhoprime: &[u8; CRHBYTES], nonce: u16) -> PolyVecL {
|
|
let mut y = PolyVecL::default();
|
|
for i in 0..L {
|
|
y[i] = self.compute_y_component(rhoprime, nonce, i as u16)
|
|
}
|
|
y
|
|
}
|
|
|
|
/// Returns the `i`-th component of `w = A * y`.
|
|
///
|
|
/// Here:
|
|
/// - `A` is a matrix (part of the key): expanded from `rho`
|
|
/// - `y` is a vector of polynomials 'sampled' when signing using `rhoprime`
|
|
///
|
|
/// Note that here `A` and `y` get recomputed in order to minimize the
|
|
/// stack usage.
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `rhoprime` - an array of bytes obrained as `SHA256(key || mu)`
|
|
/// * `nonce` - current count of the attempts to sign the given message
|
|
/// * `i` - the index of the `w` component to be computed
|
|
#[cfg(feature = "optimize_stack")]
|
|
fn compute_w_component(&self, rhoprime: &[u8; CRHBYTES], nonce: u16, i: usize) -> Poly {
|
|
let mut w_component: Poly = [0; N];
|
|
|
|
// w[i] = sum_j of A[i][j] * y[j]
|
|
for j in 0..L {
|
|
let mut y_component = self.compute_y_component(rhoprime, nonce, j as u16);
|
|
// nonce = nonce + 1;
|
|
poly::ntt(&mut y_component);
|
|
|
|
// Expand the matrix and matrix-vector multiplication
|
|
let mut mat_component: Poly = [0; N];
|
|
expand_mat_component(&self.rho, i, j, &mut mat_component);
|
|
polyvec::pointwise_acc_invmontgomery_componentwise(
|
|
&mut w_component,
|
|
&mat_component,
|
|
&y_component,
|
|
j,
|
|
);
|
|
}
|
|
poly::reduce(&mut w_component);
|
|
poly::invntt_montgomery(&mut w_component);
|
|
poly::caddq(&mut w_component);
|
|
return w_component;
|
|
}
|
|
|
|
/// Returns the vector of `K` polynomials `w = A * y`.
|
|
///
|
|
/// Here:
|
|
/// - `A` is a matrix (part of the key): expanded from `rho`
|
|
/// - `y` is a vector of polynomials 'sampled' when signing using `rhoprime`
|
|
///
|
|
/// In order to optimize the speed, we take `A` and `y` as parameters.
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `mat` - the matrix `A`
|
|
/// * `y` - the vector of polynomials `y`
|
|
#[cfg(not(feature = "optimize_stack"))]
|
|
fn compute_w(&self, mat: &[PolyVecL; K], y: &PolyVecL) -> PolyVecK {
|
|
let mut w = PolyVecK::default();
|
|
let mut yhat = y.clone();
|
|
yhat.ntt();
|
|
for i in 0..K {
|
|
polyvec::pointwise_acc_invmontgomery(&mut w[i], &mat[i], &yhat);
|
|
}
|
|
|
|
w.reduce();
|
|
w.invntt_montgomery();
|
|
w.caddq();
|
|
|
|
w
|
|
}
|
|
|
|
/// Returns t[i], where `t = A * s1 + s2`, when optimizing stack usage.
|
|
///
|
|
/// Here:
|
|
/// - `A` is a matrix (part of the key): expanded from `rho`
|
|
/// - `s1` is a vector of polynomials (part of the key)
|
|
/// - `s2` is a vector of polynomials (part of the key)
|
|
///
|
|
/// Note that this function recomputes `A`, `s1`, and `s2` in order to
|
|
/// optimize the stack usage.
|
|
///
|
|
/// # Arguments
|
|
/// * `i` - the index of the `t` component to be computed
|
|
#[cfg(feature = "optimize_stack")]
|
|
fn compute_t_component(&self, i: usize) -> Poly {
|
|
let mut t_component: Poly = [0; N];
|
|
// Sample the matrix A and compute t[i] = sum_j A[i][j] * s1[j]
|
|
for j in 0..L {
|
|
// Resample s1[j]
|
|
let mut s1_component = self.compute_s1_component(j);
|
|
poly::ntt(&mut s1_component);
|
|
|
|
let mut mat_component: Poly = [0; N];
|
|
expand_mat_component(&self.rho, i, j, &mut mat_component);
|
|
|
|
polyvec::pointwise_acc_invmontgomery_componentwise(
|
|
&mut t_component,
|
|
&mat_component,
|
|
&s1_component,
|
|
j,
|
|
);
|
|
}
|
|
|
|
poly::reduce(&mut t_component);
|
|
poly::invntt_montgomery(&mut t_component);
|
|
|
|
// Unpack s2[i], compute t[i] = sum_j A[i][j] * s1[j] + s2[i]
|
|
{
|
|
let s2_component = self.compute_s2_component(i);
|
|
poly::add_assign(&mut t_component, &s2_component);
|
|
}
|
|
poly::caddq(&mut t_component);
|
|
|
|
t_component
|
|
}
|
|
|
|
/// Returns t[i], where `t = A * s1 + s2`, when optimizing speed.
|
|
///
|
|
/// Here:
|
|
/// - `A` is a matrix (part of the key): expanded from `rho`
|
|
/// - `s1` is a vector of polynomials (part of the key)
|
|
/// - `s2` is a vector of polynomials (part of the key)
|
|
///
|
|
/// In order to optimize the speed, we take `A`, `s1` and `s2` as
|
|
/// parameters.
|
|
///
|
|
/// # Arguments
|
|
/// * `mat` - the matrix `A`
|
|
/// * `s1` - a vector of `L` polynomials in NTT format
|
|
/// * `s2` - a vector of `K` polynomials in standard format
|
|
/// * `i` - the index of the `t` component to be computed
|
|
#[cfg(not(feature = "optimize_stack"))]
|
|
fn compute_t_component(
|
|
&self,
|
|
mat: &[PolyVecL; K],
|
|
s1: &PolyVecL,
|
|
s2: &PolyVecK,
|
|
i: usize,
|
|
) -> Poly {
|
|
let mut t_component: Poly = [0; N];
|
|
|
|
polyvec::pointwise_acc_invmontgomery(&mut t_component, &mat[i], &s1);
|
|
poly::reduce(&mut t_component);
|
|
poly::invntt_montgomery(&mut t_component);
|
|
poly::add_assign(&mut t_component, &s2[i]);
|
|
poly::caddq(&mut t_component);
|
|
|
|
t_component
|
|
}
|
|
|
|
/// Returns the the vector of `K` polynomials `t = A * s1 + s2`.
|
|
///
|
|
/// Here:
|
|
/// - `A` is a matrix (part of the key): expanded from `rho`
|
|
/// - `s1` is a vector of polynomials (part of the key)
|
|
/// - `s2` is a vector of polynomials (part of the key)
|
|
///
|
|
/// In order to optimize the speed, we take `A`, `s1` and `s2` as
|
|
/// parameters.
|
|
///
|
|
/// # Arguments
|
|
/// * `mat` - the matrix `A`
|
|
/// * `s1` - a vector of `L` polynomials in NTT format
|
|
/// * `s2` - a vector of `K` polynomials in standard format
|
|
#[cfg(not(feature = "optimize_stack"))]
|
|
fn compute_t(&self, mat: &[PolyVecL; K], s1: &PolyVecL, s2: &PolyVecK) -> PolyVecK {
|
|
let mut t = PolyVecK::default();
|
|
|
|
for i in 0..K {
|
|
t[i] = self.compute_t_component(mat, s1, s2, i);
|
|
}
|
|
|
|
t
|
|
}
|
|
|
|
/// Returns the seed for generating the challenge polynomial.
|
|
///
|
|
/// The seed is obtained as `SHA256(mu || encodings of 'commit' w1)`, where:
|
|
/// - `w1` contains the high bits of `w = A * y`
|
|
/// - `A` is a matrix (part of the key): expanded from `rho`
|
|
/// - `y` is a vector of polynomials 'sampled' when signing using `rhoprime`.
|
|
///
|
|
/// In order to optimize the stack usage, this function recomputes `w`.
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `mu` - array of bytes containing the hashed input message
|
|
/// * `rhoprime` - an array of bytes obrained as `SHA256(key || mu)`
|
|
/// * `nonce` - current count of the attempts to sign the given message
|
|
#[cfg(feature = "optimize_stack")]
|
|
fn compute_c_seed(
|
|
&self,
|
|
mu: &[u8; CRHBYTES],
|
|
rhoprime: &[u8; CRHBYTES],
|
|
nonce: u16,
|
|
) -> [u8; SEEDBYTES] {
|
|
let mut hasher = Shake256::default();
|
|
hasher.process(mu);
|
|
for i in 0..K {
|
|
let w1_component = {
|
|
let w_component = self.compute_w_component(rhoprime, nonce, i);
|
|
poly::high_bits(&w_component)
|
|
};
|
|
let mut pack = [0; POLW1_SIZE_PACKED];
|
|
poly::w1_pack(&mut pack, &w1_component);
|
|
hasher.process(&pack);
|
|
}
|
|
|
|
let mut xof = hasher.xof_result();
|
|
let mut seed = [0u8; SEEDBYTES];
|
|
xof.read(&mut seed);
|
|
seed
|
|
}
|
|
|
|
/// Returns the seed for generating the challenge polynomial.
|
|
///
|
|
/// The seed is obtained as `SHA256(mu || encodings of 'commit' w1)`, where:
|
|
/// - `w1` contains the high bits of `w = A * y`
|
|
/// - `A` is a matrix (part of the key): expanded from `rho`
|
|
/// - `y` is a vector of polynomials 'sampled' when signing using `rhoprime`.
|
|
///
|
|
/// In order to optimize the speed, we take `w1` as a parameter.
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `mu` - array of bytes containing the hashed input message
|
|
/// * `w1` - an array of `K` polynomials.
|
|
#[cfg(not(feature = "optimize_stack"))]
|
|
fn compute_c_seed(&self, mu: &[u8; CRHBYTES], w1: &PolyVecK) -> [u8; SEEDBYTES] {
|
|
let mut hasher = Shake256::default();
|
|
hasher.process(mu);
|
|
for i in 0..K {
|
|
let mut pack = [0; POLW1_SIZE_PACKED];
|
|
poly::w1_pack(&mut pack, &w1[i]);
|
|
hasher.process(&pack);
|
|
}
|
|
|
|
let mut xof = hasher.xof_result();
|
|
let mut seed = [0u8; SEEDBYTES];
|
|
xof.read(&mut seed);
|
|
seed
|
|
}
|
|
|
|
/// Returns the `i`-th component of `s1` (part of the sk).
|
|
///
|
|
/// # Arguments
|
|
/// * `i` - the index of the `s1` component to be computed
|
|
fn compute_s1_component(&self, i: usize) -> Poly {
|
|
let mut s1_component = [0; N];
|
|
poly::eta_unpack(&mut s1_component, &self.s1_packed[i]);
|
|
s1_component
|
|
}
|
|
|
|
/// Returns the vector of `L` polynomials `s1` (part of sk).
|
|
#[cfg(not(feature = "optimize_stack"))]
|
|
fn compute_s1(&self) -> PolyVecL {
|
|
let mut s1 = PolyVecL::default();
|
|
|
|
for i in 0..L {
|
|
s1[i] = self.compute_s1_component(i);
|
|
}
|
|
|
|
s1
|
|
}
|
|
|
|
/// Returns the `i`-th component of `s2` (part of the sk).
|
|
///
|
|
/// # Arguments
|
|
/// * `i` - the index of the `s1` component to be computed
|
|
fn compute_s2_component(&self, i: usize) -> Poly {
|
|
let mut s2_component = [0; N];
|
|
poly::eta_unpack(&mut s2_component, &self.s2_packed[i]);
|
|
s2_component
|
|
}
|
|
|
|
/// Returns the vector of `K` polynomials `s2` (part of sk).
|
|
#[cfg(not(feature = "optimize_stack"))]
|
|
fn compute_s2(&self) -> PolyVecK {
|
|
let mut s2 = PolyVecK::default();
|
|
for i in 0..K {
|
|
s2[i] = self.compute_s2_component(i);
|
|
}
|
|
s2
|
|
}
|
|
|
|
/// Returns the `i`-th component of `z = y + c * s1`.
|
|
///
|
|
/// Here:
|
|
/// - `y` is a vector of polynomials 'sampled' when signing using `rhoprime`
|
|
/// - `c` is the challenge polynomial based on `H(mu || 'commit' w1)`
|
|
/// - `s1` is part of the secret key
|
|
///
|
|
/// Note that this function recomputes s1 and y in order to optimize the
|
|
/// stack usage.
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `rhoprime` - an array of bytes obrained as `SHA256(key || mu)`
|
|
/// * `c` - a polynomial in NTT format
|
|
/// * `nonce` - current count of the attempts to sign the given message
|
|
/// * `i` - the index of the `z` component to be computed
|
|
#[cfg(feature = "optimize_stack")]
|
|
fn compute_z_component(
|
|
&self,
|
|
rhoprime: &[u8; CRHBYTES],
|
|
c: &Poly,
|
|
nonce: u16,
|
|
i: usize,
|
|
) -> Option<Poly> {
|
|
let mut z_component: Poly;
|
|
// Compute c * s1.
|
|
{
|
|
let mut s1_component = self.compute_s1_component(i);
|
|
poly::ntt(&mut s1_component);
|
|
z_component = poly::multiply(&c, &s1_component);
|
|
}
|
|
|
|
// Sample a component of the intermediate vector y and compute c * s1 + y.
|
|
{
|
|
let y_component = self.compute_y_component(rhoprime, nonce, i as u16);
|
|
poly::add_assign(&mut z_component, &y_component);
|
|
}
|
|
poly::reduce(&mut z_component);
|
|
|
|
// Reject if z reveals secret.
|
|
if poly::chknorm(&z_component, GAMMA1 - BETA) {
|
|
None
|
|
} else {
|
|
Some(z_component)
|
|
}
|
|
}
|
|
|
|
/// Returns the vector of `L` polynomials `z = c * s1 + y`.
|
|
///
|
|
/// Here:
|
|
/// - `y` is a vector of polynomials 'sampled' when signing using `rhoprime`
|
|
/// - `c` is the challenge polynomial based on `H(mu || 'commit' w1)`
|
|
/// - `s1` is part of the secret key
|
|
///
|
|
/// In order to optimize the speed, we take `s1` and `y` as parameters
|
|
/// instead of recomputing them.
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `c` - a polynomial in NTT format
|
|
/// * `s1` - a vector of `L` polynomials in NTT format
|
|
/// * `y` - a vector of `L` polynomials in standard format
|
|
#[cfg(not(feature = "optimize_stack"))]
|
|
fn compute_z(&self, c: &Poly, s1: &PolyVecL, y: &PolyVecL) -> Option<PolyVecL> {
|
|
let mut z = PolyVecL::default();
|
|
for i in 0..L {
|
|
z[i] = poly::multiply(&c, &s1[i]);
|
|
}
|
|
|
|
z.add_assign(y);
|
|
z.reduce();
|
|
if z.chknorm(GAMMA1 - BETA) {
|
|
None
|
|
} else {
|
|
Some(z)
|
|
}
|
|
}
|
|
|
|
/// Returns the `i`-th component of `w0cs2 = w0 - c * s2`.
|
|
///
|
|
/// Here:
|
|
/// - `w0` contains the low bits of `w = A * y`
|
|
/// - `c` is the challenge polynomial based on `H(mu || 'commit' w1)`
|
|
/// - `s2` is part of the secret key
|
|
///
|
|
/// Note that this function recomputes s2 to minimize the stack usage.
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `rhoprime` - an array of bytes obrained as `SHA256(key || mu)`
|
|
/// * `c` - a polynomial in NTT format
|
|
/// * `nonce` - current count of the attempts to sign the given message
|
|
/// * `i` - the index of the `w0cs2` component to be computed
|
|
#[cfg(feature = "optimize_stack")]
|
|
fn compute_w0cs2_component(&self, w_component: &Poly, c: &Poly, i: usize) -> Option<Poly> {
|
|
let mut w0cs2_component: Poly = [0; N];
|
|
{
|
|
// c * s2
|
|
let cs2_component: Poly;
|
|
{
|
|
let mut s2_component = self.compute_s2_component(i);
|
|
poly::ntt(&mut s2_component);
|
|
cs2_component = poly::multiply(&c, &s2_component);
|
|
}
|
|
|
|
// w0cs2 = w0 - cs2 = w0 - c * s2
|
|
{
|
|
let w0_component = poly::low_bits(&w_component);
|
|
poly::sub(&mut w0cs2_component, &w0_component, &cs2_component);
|
|
}
|
|
|
|
poly::reduce(&mut w0cs2_component);
|
|
}
|
|
|
|
// Reject the attempt if the norm of w0cs2 is too high.
|
|
if poly::chknorm(&w0cs2_component, GAMMA2 - BETA) {
|
|
None
|
|
} else {
|
|
Some(w0cs2_component)
|
|
}
|
|
}
|
|
|
|
/// Returns the vector of `K` polynomials `w0cs2 = w0 - c * s2`.
|
|
///
|
|
/// Here:
|
|
/// - `w0` contains the low bits of `w = A * y`
|
|
/// - `c` is the challenge polynomial based on `H(mu || 'commit' w1)`
|
|
/// - `s2` is part of the secret key
|
|
///
|
|
/// In order to optimize the speed, we take `w0` and `s2` as parameters
|
|
/// instead of recomputing them.
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `w0` - an array of `K` polynomials
|
|
/// * `c` - a polynomial in NTT format
|
|
/// * `s2` - a vector of K polynomials in NTT format
|
|
#[cfg(not(feature = "optimize_stack"))]
|
|
fn compute_w0cs2(&self, w0: &PolyVecK, c: &Poly, s2: &PolyVecK) -> Option<PolyVecK> {
|
|
// Compute c * s2
|
|
let mut cs2 = PolyVecK::default();
|
|
for i in 0..K {
|
|
cs2[i] = poly::multiply(&c, &s2[i]);
|
|
}
|
|
|
|
// Compute w0 - cs2
|
|
let mut w0cs2 = PolyVecK::default();
|
|
w0cs2.with_sub(&w0, &cs2);
|
|
w0cs2.reduce();
|
|
|
|
if w0cs2.chknorm(GAMMA2 - BETA) {
|
|
None
|
|
} else {
|
|
Some(w0cs2)
|
|
}
|
|
}
|
|
|
|
/// Returns the i-th component of `ct0 = c * t0`.
|
|
///
|
|
/// Here:
|
|
/// - `c` is the challenge polynomial based on `H(mu || 'commit' w1)`
|
|
/// - `t0` is `t mod 2^D`, where `t = A * s1 + s2`
|
|
///
|
|
/// Note that this function recomputes `A`, `s1`, and `s2`, in order to
|
|
/// minimize the stack usage.
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `c` - a polynomial in NTT format
|
|
/// * `i` - the index of the `ct0` component to be computed
|
|
#[cfg(feature = "optimize_stack")]
|
|
fn compute_ct0_component(&self, c: &Poly, i: usize) -> Option<Poly> {
|
|
let ct0_component: Poly;
|
|
{
|
|
let mut t0_component = poly::power2round_remainder(&self.compute_t_component(i));
|
|
poly::ntt(&mut t0_component);
|
|
ct0_component = poly::multiply(&c, &t0_component);
|
|
}
|
|
|
|
if poly::chknorm(&ct0_component, GAMMA2) {
|
|
None
|
|
} else {
|
|
Some(ct0_component)
|
|
}
|
|
}
|
|
|
|
/// Returns the vector of `K` polynomials `ct0 = c * t0`.
|
|
///
|
|
/// Here:
|
|
/// - `c` is the challenge polynomial based on `H(mu || 'commit' w1)`
|
|
/// - `t0` is `t mod 2^D`, where `t = A * s1 + s2`
|
|
///
|
|
/// This function takes `A`, `s1` and `s2` as parameters in order to
|
|
/// compute `t` with optimize speed.
|
|
///
|
|
/// # Arguments
|
|
/// * `c` - a polynomial in NTT format
|
|
/// * `mat` - the matrix `A`
|
|
/// * `s1` - a vector of `L` polynomials in NTT format
|
|
/// * `s2` - a vector of `K` polynomials
|
|
#[cfg(not(feature = "optimize_stack"))]
|
|
fn compute_ct0(
|
|
&self,
|
|
c: &Poly,
|
|
mat: &[PolyVecL; K],
|
|
s1: &PolyVecL,
|
|
s2: &PolyVecK,
|
|
) -> Option<PolyVecK> {
|
|
let t = self.compute_t(mat, s1, s2);
|
|
let mut t0 = PolyVecK::default();
|
|
t.power2round_remainder(&mut t0);
|
|
t0.ntt();
|
|
let mut ct0 = PolyVecK::default();
|
|
for i in 0..K {
|
|
ct0[i] = poly::multiply(&c, &t0[i]);
|
|
}
|
|
|
|
ct0.reduce();
|
|
if ct0.chknorm(GAMMA2) {
|
|
None
|
|
} else {
|
|
Some(ct0)
|
|
}
|
|
}
|
|
|
|
/// Attempts to compute a signature with the given parameters.
|
|
///
|
|
/// The signature is computed deterministically using:
|
|
/// - `mu`(the hashed message)
|
|
/// - the `nonce`
|
|
///
|
|
/// This function aims to minimize the stack usage, at the cost of speed.
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `mu` - array containing the hashed input message
|
|
/// * `nonce` - current count of the attempts to sign the given message
|
|
#[cfg(feature = "optimize_stack")]
|
|
fn try_sign_with_nonce(
|
|
&self,
|
|
mu: &[u8; CRHBYTES],
|
|
nonce: u16,
|
|
) -> Option<[u8; SIG_SIZE_PACKED]> {
|
|
let mut sig = [0; SIG_SIZE_PACKED];
|
|
|
|
let mut rhoprime = [0u8; CRHBYTES];
|
|
shake256!(&mut rhoprime; &self.key, mu);
|
|
|
|
let c_seed = self.compute_c_seed(mu, &rhoprime, nonce);
|
|
let mut chat: Poly = poly::build_challenge_from_seed(&c_seed);
|
|
packing::sign::pack_c(&mut sig, &c_seed);
|
|
poly::ntt(&mut chat);
|
|
|
|
let mut hint = 0;
|
|
let mut hint_non_zero_coeff_index = 0;
|
|
|
|
for i in 0..K {
|
|
// The verifier needs the hint for the difference between
|
|
// tmp = w0 - c * s2 + c * t0 and w1 to recompute w.
|
|
let mut h_component: Poly = [0; N];
|
|
{
|
|
// Recompute the i-th component of w1: the high bits of w = A * y
|
|
let w_component: Poly = self.compute_w_component(&rhoprime, nonce, i);
|
|
|
|
// Compute i-th component of w0cs2 = w0 - c * s2, where w0 = the low bits of w
|
|
let w0cs2_component: Poly = self.compute_w0cs2_component(&w_component, &chat, i)?;
|
|
|
|
let mut tmp_component: Poly = [0; N];
|
|
{
|
|
// Compute the i-th component of ct0 = c * t0
|
|
let ct0_component: Poly = self.compute_ct0_component(&chat, i)?;
|
|
poly::add(&mut tmp_component, &w0cs2_component, &ct0_component);
|
|
}
|
|
|
|
let w1_component = poly::high_bits(&w_component);
|
|
|
|
hint += poly::make_hint(&tmp_component, &w1_component, &mut h_component);
|
|
}
|
|
|
|
if hint > OMEGA {
|
|
return None;
|
|
}
|
|
|
|
packing::sign::pack_h_component(
|
|
&mut sig,
|
|
&h_component,
|
|
i,
|
|
&mut hint_non_zero_coeff_index,
|
|
);
|
|
}
|
|
|
|
// Computing z = y + cs1
|
|
for i in 0..L {
|
|
let z_component: Poly = self.compute_z_component(&rhoprime, &chat, nonce, i)?;
|
|
packing::sign::pack_z_component(&mut sig, &z_component, i);
|
|
}
|
|
|
|
Some(sig)
|
|
}
|
|
|
|
/// Attempts to compute a signature with the given parameters.
|
|
///
|
|
/// The signature is computed deterministically using:
|
|
/// - `mu`(the hashed message)
|
|
/// - the `nonce`
|
|
///
|
|
/// This function aims to minimize the speed, at the cost of stack usage.
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `mu` - array containing the hashed input message
|
|
/// * `nonce` - current count of the attempts to sign the given message
|
|
#[cfg(not(feature = "optimize_stack"))]
|
|
fn try_sign_with_nonce(
|
|
&self,
|
|
mu: &[u8; CRHBYTES],
|
|
nonce: u16,
|
|
) -> Option<[u8; SIG_SIZE_PACKED]> {
|
|
let mut rhoprime = [0u8; CRHBYTES];
|
|
shake256!(&mut rhoprime; &self.key, mu);
|
|
|
|
let mut sig = [0; SIG_SIZE_PACKED];
|
|
|
|
// Sample intermediate vector
|
|
let y = self.compute_y(&rhoprime, nonce);
|
|
|
|
// Matrix-vector multiplication
|
|
let mut mat = [PolyVecL::default(); K];
|
|
expand_mat(&self.rho, &mut mat);
|
|
|
|
let w = self.compute_w(&mat, &y);
|
|
|
|
// Decompose w and call the random oracle
|
|
let (mut w0, mut w1) = (PolyVecK::default(), PolyVecK::default());
|
|
w.decompose(&mut w0, &mut w1);
|
|
|
|
// Compute challenge
|
|
let c_seed = self.compute_c_seed(mu, &w1);
|
|
packing::sign::pack_c(&mut sig, &c_seed);
|
|
let mut c: Poly = poly::build_challenge_from_seed(&c_seed);
|
|
poly::ntt(&mut c);
|
|
|
|
// Compute z, reject if it reveals secret
|
|
let mut s1 = self.compute_s1();
|
|
s1.ntt();
|
|
let z = self.compute_z(&c, &s1, &y)?;
|
|
packing::sign::pack_z(&mut sig, &z);
|
|
|
|
// Compute ct0 = c * t0
|
|
let mut s2 = self.compute_s2();
|
|
let ct0 = self.compute_ct0(&c, &mat, &s1, &s2)?;
|
|
|
|
// Compute w0 - c * s2, reject if w1 can not be computed from it
|
|
s2.ntt();
|
|
let w0cs2 = self.compute_w0cs2(&w0, &c, &s2)?;
|
|
|
|
// The verifier needs the hint for the difference between
|
|
// tmp = w0 - c * s2 + c * t0 and w1 to recompute w.
|
|
let mut tmp = PolyVecK::default();
|
|
tmp.with_add(&w0cs2, &ct0);
|
|
let mut h = PolyVecK::default();
|
|
let hint = polyvec::make_hint(&tmp, &w1, &mut h);
|
|
if hint > OMEGA {
|
|
return None;
|
|
};
|
|
packing::sign::pack_h(&mut sig, &h);
|
|
|
|
return Some(sig);
|
|
}
|
|
|
|
/// Returns a signature for the given message.
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `m` - the message to be signed.
|
|
pub fn sign(&self, m: &[u8]) -> [u8; SIG_SIZE_PACKED] {
|
|
// Compute CRH(tr, msg)
|
|
let mut mu = [0u8; CRHBYTES];
|
|
shake256!(&mut mu; &self.tr, m);
|
|
|
|
let mut nonce = 0;
|
|
|
|
// The probability that multiple iterations are needed is very low.
|
|
// More details can be found in section 3.2:
|
|
// https://eprint.iacr.org/2017/633.pdf
|
|
// TODO(dianamin): Add an artificial break after some number of iterations.
|
|
loop {
|
|
match self.try_sign_with_nonce(&mu, nonce) {
|
|
Some(sig) => break sig,
|
|
None => nonce = nonce + 1 as u16,
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Default for PubKey {
|
|
fn default() -> PubKey {
|
|
PubKey {
|
|
rho: [0; SEEDBYTES],
|
|
t1_packed: [[0; POLT1_SIZE_PACKED]; K],
|
|
}
|
|
}
|
|
}
|
|
|
|
impl PubKey {
|
|
/// Encodes the public key into an array of bytes.
|
|
///
|
|
/// Fills the bytes array as follows:
|
|
/// `rho || encodings of t1`
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `bytes` - an array of bytes where the encoding will be stored.
|
|
pub fn to_bytes(&self, bytes: &mut [u8; PK_SIZE_PACKED]) {
|
|
let mut offset = 0;
|
|
let mut push = |data: &[u8]| {
|
|
bytes[offset..][..data.len()].copy_from_slice(data);
|
|
offset += data.len();
|
|
};
|
|
|
|
push(&self.rho);
|
|
for i in 0..K {
|
|
push(&self.t1_packed[i]);
|
|
}
|
|
}
|
|
|
|
/// Decodes the public key from an array of bytes.
|
|
///
|
|
/// Extracts the fields from a bytes array with the following shape:
|
|
/// `rho || encodings of t1`
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `bytes` - the array of bytes containing the encoding.
|
|
pub fn from_bytes(bytes: &[u8; PK_SIZE_PACKED]) -> PubKey {
|
|
let mut offset = 0;
|
|
let mut pull = |data: &mut [u8]| {
|
|
data.copy_from_slice(&bytes[offset..][..data.len()]);
|
|
offset += data.len();
|
|
};
|
|
|
|
let mut pk = PubKey::default();
|
|
pull(&mut pk.rho);
|
|
for i in 0..K {
|
|
pull(&mut pk.t1_packed[i]);
|
|
}
|
|
pk
|
|
}
|
|
|
|
/// Computes the seed needed to generate c: `SHA256(mu || encodings of w1)`.
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `mu` - the hashed message
|
|
/// * `w1` - a vector of `K` polynomials
|
|
fn compute_c_seed(&self, mu: &[u8; CRHBYTES], w1: &PolyVecK) -> [u8; SEEDBYTES] {
|
|
let mut outbuf = [0u8; SEEDBYTES];
|
|
let mut w1pack = [0u8; K * POLW1_SIZE_PACKED];
|
|
for (i, pack) in w1pack.chunks_mut(POLW1_SIZE_PACKED).enumerate() {
|
|
let pack = array_mut_ref!(pack, 0, POLW1_SIZE_PACKED);
|
|
poly::w1_pack(pack, &w1[i]);
|
|
}
|
|
let mut hasher = Shake256::default();
|
|
hasher.process(mu);
|
|
hasher.process(&w1pack);
|
|
let mut xof = hasher.xof_result();
|
|
xof.read(&mut outbuf);
|
|
|
|
outbuf
|
|
}
|
|
|
|
// TODO(dianamin): Refactor this function.
|
|
/// Verifies the given signature for the given message.
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `m` - the message
|
|
/// * `sig` - the signature to be verified
|
|
pub fn verify(&self, m: &[u8], sig: &[u8; SIG_SIZE_PACKED]) -> bool {
|
|
let mut pk = [0; PK_SIZE_PACKED];
|
|
self.to_bytes(&mut pk);
|
|
let (mut rho, mut mu) = ([0; SEEDBYTES], [0; CRHBYTES]);
|
|
|
|
let mut c = [0u8; SEEDBYTES];
|
|
let mut z = PolyVecL::default();
|
|
let (mut t1, mut w1, mut h) = Default::default();
|
|
let (mut tmp1, mut tmp2) = (PolyVecK::default(), PolyVecK::default());
|
|
|
|
packing::pk::unpack(&pk, &mut rho, &mut t1);
|
|
let r = packing::sign::unpack(sig, &mut c, &mut z, &mut h);
|
|
|
|
if !r {
|
|
return false;
|
|
};
|
|
if z.chknorm(GAMMA1 - BETA) {
|
|
return false;
|
|
};
|
|
|
|
// Compute CRH(CRH(rho, t1), msg)
|
|
shake256!(&mut mu[0..SEEDBYTES]; &pk);
|
|
shake256!(&mut mu[0..CRHBYTES]; &mu[0..SEEDBYTES], m);
|
|
|
|
// Expand matrix and matrix-vector multiplication; compute Az - c2^dt1
|
|
z.ntt();
|
|
for i in 0..K {
|
|
for j in 0..L {
|
|
let mut mat_component: Poly = [0; N];
|
|
expand_mat_component(&self.rho, i, j, &mut mat_component);
|
|
polyvec::pointwise_acc_invmontgomery_componentwise(
|
|
&mut tmp1[i],
|
|
&mat_component,
|
|
&z[j],
|
|
j,
|
|
);
|
|
}
|
|
}
|
|
|
|
let cp = poly::build_challenge_from_seed(&c);
|
|
let mut chat = cp.clone();
|
|
poly::ntt(&mut chat);
|
|
t1.shift_left();
|
|
t1.ntt();
|
|
for i in 0..K {
|
|
poly::pointwise_invmontgomery(&mut tmp2[i], &chat, &t1[i]);
|
|
}
|
|
|
|
let mut tmp = PolyVecK::default();
|
|
tmp.with_sub(&tmp1, &tmp2);
|
|
tmp.reduce();
|
|
tmp.invntt_montgomery();
|
|
|
|
// Reconstruct w1
|
|
tmp.caddq();
|
|
polyvec::use_hint(&mut w1, &tmp, &h);
|
|
|
|
// Call random oracle and verify challenge
|
|
let c2 = self.compute_c_seed(&mu, &w1);
|
|
|
|
for i in 0..SEEDBYTES {
|
|
if c[i] != c2[i] {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
true
|
|
}
|
|
}
|