//! scrypt memory requirements scale linearly with parameters `N` and `r`.
//! This makes it unsuitable for embedded environments with the parameters
//! used in Spectre.
//! Our work-around is to derive the Spectre _user key_ using scrypt on the
//! host, encrypt it with XOR using a key derived using argon2, which
//! has parameters for specifying the memory and time requirements separately.
//! This encrypted key is then stored on the keyboard, to be decrypted again.
#![cfg_attr(not(feature = "std"), no_std)]
#![feature(allocator_api)]

extern crate alloc;

use alloc::alloc::Allocator;
use alloc::vec::Vec;
use argon2::{Algorithm, Argon2, ParamsBuilder, Version};

use crate::blocks::Argon2Blocks;

pub mod blocks;

pub type Key = [u8; 64];

/// KiB used by the argon2 algorithm.
/// Lower than the default, to fit in the constrained memory of embedded devices.
pub const ARGON2_M_COST: u32 = 1024;
/// Compensate the difficulty by increasing the iterations proportionally.
pub const ARGON2_T_COST: u32 =
    argon2::Params::DEFAULT_T_COST * argon2::Params::DEFAULT_M_COST / ARGON2_M_COST;
pub const ARGON2_P_COST: u32 = argon2::Params::DEFAULT_P_COST;
pub const ARGON2_SALT_PREFIX: &[u8] = b"acid-firmware\0";

pub fn derive_encryption_key(
    unprefixed_salt: &[u8],
    secret: &[u8],
    allocator: impl Allocator,
) -> Key {
    let argon2 = Argon2::new(
        Algorithm::Argon2id,
        Version::default(),
        ParamsBuilder::default()
            .m_cost(ARGON2_M_COST)
            .t_cost(ARGON2_T_COST)
            .p_cost(ARGON2_P_COST)
            .build()
            .unwrap(),
    );
    let mut blocks = Argon2Blocks::new_in(ARGON2_M_COST as usize, &allocator).unwrap();
    let mut key: Key = [0u8; _];
    // Salt is prefixed to form a salt that is long enough for Argon2.
    let mut salt =
        Vec::with_capacity_in(unprefixed_salt.len() + ARGON2_SALT_PREFIX.len(), &allocator);
    salt.extend_from_slice(ARGON2_SALT_PREFIX);
    salt.extend_from_slice(unprefixed_salt);
    argon2
        .hash_password_into_with_memory(secret, &salt, &mut key, &mut blocks)
        .unwrap();

    key
}

pub fn decrypt_with(data: &mut Key, key: &Key) {
    for (dst_byte, user_byte) in core::iter::zip(data.iter_mut(), key.iter()) {
        *dst_byte ^= *user_byte;
    }
}

pub fn encrypt_with(data: &mut Key, key: &Key) {
    decrypt_with(data, key);
}
