rustkey 0.5.0

rusTkey — rust library for tillitis TKey application development
Documentation
//! Simplified implementation of Blake2s
//!
//! Origin: RustCrypto/Hashes - Blake2 <https://github.com/RustCrypto/hashes>
//! License: MIT OR Apache License 2.0
//! commit: 1f727ce37ff40fa0cce84eb8543a45bdd3ca4a4e (tag: blake2-v0.10.6)

const IV: [u32; 8] = [
    0x6A09_E667,
    0xBB67_AE85,
    0x3C6E_F372,
    0xA54F_F53A,
    0x510E_527F,
    0x9B05_688C,
    0x1F83_D9AB,
    0x5BE0_CD19,
];

/*
pub const BLAKE2S_BLOCKBYTES : usize = 64;
pub const BLAKE2S_OUTBYTES : usize = 32;
pub const BLAKE2S_KEYBYTES : usize = 32;
pub const BLAKE2S_SALTBYTES : usize = 8;
pub const BLAKE2S_PERSONALBYTES : usize = 8;
*/

#[derive(Debug)]
pub enum Error {
    InvalidInput,
}

#[derive(Clone)]
#[doc = "Blake2s instance with a variable output."]
pub struct Blake2s {
    b: [u8; 64],
    h: [u32; 8],
    t: u64,
    c: u8,
    outlen: u8,
}

impl Blake2s {
    /// Creates a new context with the full set of sequential-mode parameters.
    #[allow(clippy::cast_possible_truncation)]
    fn new_with_params(
        outlen: usize,
        salt: &[u8],
        persona: &[u8],
        key_size: usize,
    ) -> Result<Self, Error> {
        if outlen == 0 || outlen > 32 || key_size > 32 || salt.len() > 8 || persona.len() > 8 {
            return Err(Error::InvalidInput);
        }

        // Build a parameter block
        let mut p = [0u32; 8];
        p[0] = 0x0101_0000 ^ (key_size << 8) as u32 ^ outlen as u32;

        // salt is two words long
        let mut padded = [0u8; 8];
        padded[..salt.len()].copy_from_slice(salt);
        p[4] = u32::from_le_bytes([padded[0], padded[1], padded[2], padded[3]]);
        p[5] = u32::from_le_bytes([padded[4], padded[5], padded[6], padded[7]]);

        // persona is two words long
        padded.fill(0);
        padded[..persona.len()].copy_from_slice(persona);
        p[6] = u32::from_le_bytes([padded[0], padded[1], padded[2], padded[3]]);
        p[7] = u32::from_le_bytes([padded[4], padded[5], padded[6], padded[7]]);

        p[0] ^= IV[0];
        p[1] ^= IV[1];
        p[2] ^= IV[2];
        p[3] ^= IV[3];
        p[4] ^= IV[4];
        p[5] ^= IV[5];
        p[6] ^= IV[6];
        p[7] ^= IV[7];

        Ok(Self {
            b: [0u8; 64],
            h: p,
            t: 0,
            c: 0,
            outlen: outlen as u8,
        })
    }

    /// `new` creates a new initialized Blake2s instance with all parameters exposed. (Refer to
    /// BLAKE2 publication for details on intended use.)
    ///
    /// # Errors
    /// In case of invalid input arguments, such as lengths.
    #[allow(clippy::cast_possible_truncation)]
    pub fn new(outlen: usize, salt: &[u8], persona: &[u8], key: &[u8]) -> Result<Self, Error> {
        let mut ctx = Self::new_with_params(outlen, salt, persona, key.len())?;
        if !key.is_empty() {
            ctx.update(key);
            // `b` is initialized to 0 at creation, so padding is not necessary.
            ctx.c = 64;
        }
        Ok(ctx)
    }

    /// `new_with_defaults` creates a new initialized Blake2s instance with all-zero salt and
    /// personalization.
    ///
    /// # Errors
    /// In case of invalid input arguments, such as lengths.
    pub fn new_with_defaults(outlen: usize, key: &[u8]) -> Result<Self, Error> {
        Self::new(outlen, &[], &[], key)
    }

    /// `finalize_with_flag` finalizes an ongoing hash computation with the ability to specify the
    /// flag `f1`. Refer to the BLAKE2 publication for details concerning proper use.
    ///
    /// # Errors
    /// In case of invalid input arguments.
    pub fn finalize_with_flag(&mut self, out: &mut [u8], f1: u32) -> Result<(), Error> {
        if out.len() != self.outlen as usize {
            return Err(Error::InvalidInput);
        }
        self.t += self.c as u64;
        while self.c < 64 {
            self.b[self.c as usize] = 0;
            self.c += 1;
        }
        self.compress(!0, f1);
        for i in 0..self.outlen as usize {
            out[i] = ((self.h[i >> 2] >> (8 * (i & 3))) & 0xff) as u8;
        }
        Ok(())
    }

    /// Finalize a hash computation and output hash digest into `out`.
    ///
    /// Note: `out` is expected to be of the exact same length as the originally indicated output
    /// length, a value between 1 and 32.
    ///
    /// # Errors
    /// In case of invalid input arguments.
    #[inline]
    pub fn finalize(&mut self, out: &mut [u8]) -> Result<(), Error> {
        self.finalize_with_flag(out, 0)
    }

    #[allow(clippy::too_many_lines)]
    fn compress(&mut self, f0: u32, f1: u32) {
        const SIGMA: [[u8; 16]; 10] = [
            [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
            [14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3],
            [11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4],
            [7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8],
            [9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13],
            [2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9],
            [12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11],
            [13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10],
            [6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5],
            [10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0],
        ];

        fn quarter_round(v: &mut [u32; 16], rd: u32, rb: u32, m: [u32; 4]) {
            v[0] = v[0].wrapping_add(v[4]).wrapping_add(m[0]);
            v[1] = v[1].wrapping_add(v[5]).wrapping_add(m[1]);
            v[2] = v[2].wrapping_add(v[6]).wrapping_add(m[2]);
            v[3] = v[3].wrapping_add(v[7]).wrapping_add(m[3]);
            v[12] = (v[12] ^ v[0]).rotate_right(rd);
            v[13] = (v[13] ^ v[1]).rotate_right(rd);
            v[14] = (v[14] ^ v[2]).rotate_right(rd);
            v[15] = (v[15] ^ v[3]).rotate_right(rd);
            v[8] = v[8].wrapping_add(v[12]);
            v[9] = v[9].wrapping_add(v[13]);
            v[10] = v[10].wrapping_add(v[14]);
            v[11] = v[11].wrapping_add(v[15]);
            v[4] = (v[4] ^ v[8]).rotate_right(rb);
            v[5] = (v[5] ^ v[9]).rotate_right(rb);
            v[6] = (v[6] ^ v[10]).rotate_right(rb);
            v[7] = (v[7] ^ v[11]).rotate_right(rb);
        }

        fn round(v: &mut [u32; 16], m: &[u32; 16], s: &[u8; 16]) {
            quarter_round(
                v,
                16,
                12,
                [
                    m[s[0] as usize],
                    m[s[2] as usize],
                    m[s[4] as usize],
                    m[s[6] as usize],
                ],
            );
            quarter_round(
                v,
                8,
                7,
                [
                    m[s[1] as usize],
                    m[s[3] as usize],
                    m[s[5] as usize],
                    m[s[7] as usize],
                ],
            );
            (v[4], v[5], v[6], v[7]) = (v[5], v[6], v[7], v[4]);
            (v[8], v[9], v[10], v[11]) = (v[10], v[11], v[8], v[9]);
            (v[12], v[13], v[14], v[15]) = (v[15], v[12], v[13], v[14]);
            quarter_round(
                v,
                16,
                12,
                [
                    m[s[8] as usize],
                    m[s[10] as usize],
                    m[s[12] as usize],
                    m[s[14] as usize],
                ],
            );
            quarter_round(
                v,
                8,
                7,
                [
                    m[s[9] as usize],
                    m[s[11] as usize],
                    m[s[13] as usize],
                    m[s[15] as usize],
                ],
            );
            (v[5], v[6], v[7], v[4]) = (v[4], v[5], v[6], v[7]);
            (v[10], v[11], v[8], v[9]) = (v[8], v[9], v[10], v[11]);
            (v[15], v[12], v[13], v[14]) = (v[12], v[13], v[14], v[15]);
        }

        let mut m = [0u32; 16];
        for (v, chunk) in m.iter_mut().zip(self.b.chunks_exact(4)) {
            *v = u32::from_le_bytes(chunk.try_into().unwrap());
        }
        let h = &mut self.h;

        let t0 = (self.t & 0xffff_ffff) as u32;
        let t1 = (self.t >> 32) as u32;

        let mut v = [
            h[0],
            h[1],
            h[2],
            h[3],
            h[4],
            h[5],
            h[6],
            h[7],
            IV[0],
            IV[1],
            IV[2],
            IV[3],
            IV[4] ^ t0,
            IV[5] ^ t1,
            IV[6] ^ f0,
            IV[7] ^ f1,
        ];

        // Note: the use of loops, below, make the code-base significantly smaller. I have not
        // tested for change in performance.

        for i in 0..SIGMA.len() {
            round(&mut v, &m, &SIGMA[i]);
        }

        for i in 0..h.len() {
            h[i] ^= v[i] ^ v[i + 8];
        }
    }

    pub fn update(&mut self, data: &[u8]) {
        for v in data {
            if self.c == 64 {
                self.t += self.c as u64;
                self.compress(0, 0);
                self.c = 0;
            }
            self.b[self.c as usize] = *v;
            self.c += 1;
        }
    }
}

/// blake2s is a one-shot Blake2s hash calculation.
///
/// - `out` is the output-buffer and should be between 1 and 32 (inclusive) bytes in size. The
///   output-buffer is expected to be exactly sliced.
///
/// # Errors
/// In case of invalid input arguments.
///
/// # Panics
/// - In case of exceedingly large out-buffer.
#[allow(clippy::cast_possible_truncation)]
pub fn blake2s(out: &mut [u8], key: &[u8], data: &[u8]) -> Result<(), Error> {
    let mut ctx = Blake2s::new(out.len(), &[], &[], key)?;
    ctx.update(data);
    ctx.finalize(out)
}

#[cfg(test)]
mod tests {
    use super::{blake2s, Blake2s};

    const GRAND_HASH: [u8; 32] = [
        0x6A, 0x41, 0x1F, 0x08, 0xCE, 0x25, 0xAD, 0xCD, 0xFB, 0x02, 0xAB, 0xA6, 0x41, 0x45, 0x1C,
        0xEC, 0x53, 0xC5, 0x98, 0xB2, 0x4F, 0x4F, 0xC7, 0x87, 0xFB, 0xDC, 0x88, 0x79, 0x7F, 0x4C,
        0x1D, 0xFE,
    ];
    const DIGEST_LENGTHS: [usize; 4] = [16, 20, 28, 32];
    const INPUT_LENGTHS: [usize; 6] = [0, 3, 64, 65, 255, 1024];

    fn selftest_seq(out: &mut [u8], seed: u32) {
        let mut t: u32;
        let mut a = 0xDEAD_4BADu32.overflowing_mul(seed).0;
        let mut b = 1u32;

        for i in 0..out.len() {
            t = a.overflowing_add(b).0;
            a = b;
            b = t;
            out[i] = ((t >> 24) & 0xff) as u8;
        }
    }

    #[test]
    fn selftest() {
        let mut ctx = Blake2s::new_with_defaults(32, &[]).unwrap();
        let mut input = [0u8; 1024];
        let mut digest = [0u8; 32];
        let mut key = [0u8; 32];

        for i in 0..4 {
            let outlen = DIGEST_LENGTHS[i];
            for j in 0..6 {
                let inlen = INPUT_LENGTHS[j];
                selftest_seq(&mut input[..inlen], inlen as u32);
                blake2s(&mut digest[..outlen], &[], &input[..inlen]).unwrap();
                ctx.update(&digest[..outlen]);
                selftest_seq(&mut key[..outlen], outlen as u32);
                blake2s(&mut digest[..outlen], &key[..outlen], &input[..inlen]).unwrap();
                ctx.update(&digest[..outlen]);
            }
        }

        ctx.finalize(&mut digest).unwrap();
        assert_eq!(digest, GRAND_HASH);
    }

    #[test]
    fn test_blake2s256_unkeyed_abc() {
        let mut result = [0u8; 32];
        blake2s(&mut result, &[], b"abc").unwrap();
        assert_eq!(
            result,
            [
                0x50, 0x8c, 0x5e, 0x8c, 0x32, 0x7c, 0x14, 0xe2, 0xe1, 0xa7, 0x2b, 0xa3, 0x4e, 0xeb,
                0x45, 0x2f, 0x37, 0x45, 0x8b, 0x20, 0x9e, 0xd6, 0x3a, 0x29, 0x4d, 0x99, 0x9b, 0x4c,
                0x86, 0x67, 0x59, 0x82
            ]
        );
    }
}