4
\$\begingroup\$

I just finished making an implementation of a SHA256 hashing function (https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.180-4.pdf) in Rust and I was looking for some feedback.

As far as I can tell it works properly, but I haven't used Rust before so I'm looking for some advice regarding the style. Performance advice would also be nice, it runs a bit slower than my implementation in C (1.8 vs 1.4 seconds for some test file I have). In my testing I ran with cargo run --release inputfile.txt.

The program is in two parts, main.rs and shafuncs.rs

main.rs

use std::io::Read;
use std::fs::File;
use std::num::Wrapping;
use std::env;

mod shafuncs;
use shafuncs::CHUNKBYTES;

macro_rules! wraparray {
    ( $( $x:expr),* ) => {
        [$(Wrapping($x)),*]
    }
}

fn main() {
    let args: Vec<String> = env::args().collect();
    let filename = &args[1];
    let mut f = File::open(filename).expect("Could not open file.");

    let mut buffer: [u8; CHUNKBYTES] = [0; CHUNKBYTES];
    let mut readlen;
    let mut messagelength : u64 = 0;

    let mut digest: [Wrapping<u32>; 8]= wraparray![
            0x6a09e667,
            0xbb67ae85,
            0x3c6ef372,
            0xa54ff53a,
            0x510e527f,
            0x9b05688c,
            0x1f83d9ab,
            0x5be0cd19];


    while {readlen = f.read(&mut buffer).expect("Could not read from file!"); readlen==CHUNKBYTES} {
        messagelength += readlen as u64;
        hashround(&mut digest, buffer)
    }
    messagelength += readlen as u64;
    shafuncs::padmessage(&mut buffer, readlen, messagelength);
    hashround(&mut digest, buffer);

    // Final output
    for i in digest.iter() {
        print!("{:08x}", i);
    }
    println!("  {}", filename);
}



fn hashround(digest: &mut [Wrapping<u32>; 8], bytebuffer: [u8; CHUNKBYTES]) {
    let mut t1: Wrapping<u32>;
    let mut t2: Wrapping<u32>;

    let wordbuffer = shafuncs::bytestowords(bytebuffer);

    // Message Schedule
    let w = shafuncs::message_schedule(wordbuffer);

    let mut a: Wrapping<u32> = digest[0];
    let mut b: Wrapping<u32> = digest[1];
    let mut c: Wrapping<u32> = digest[2];
    let mut d: Wrapping<u32> = digest[3];
    let mut e: Wrapping<u32> = digest[4];
    let mut f: Wrapping<u32> = digest[5];
    let mut g: Wrapping<u32> = digest[6];
    let mut h: Wrapping<u32> = digest[7];



    for t in 0..64 {
        t1 = h + shafuncs::ls1(e) + shafuncs::ch(e,f,g) + K[t] + w[t];
        t2 = shafuncs::ls0(a) + shafuncs::maj(a,b,c);
        h = g;
        g = f;
        f = e;
        e = d+t1;
        d = c;
        c = b;
        b = a;
        a = t1+t2;
    }


    digest[0] += a;
    digest[1] += b;
    digest[2] += c;
    digest[3] += d;
    digest[4] += e;
    digest[5] += f;
    digest[6] += g;
    digest[7] += h;

}



const K: [Wrapping<u32>; 64] = wraparray![

    0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
    0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
    0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
    0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
    0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
    0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
    0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
    0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
    ];

shafuncs.rs

use std::num::Wrapping;
const WORDSIZE_BITS: usize = 32;
const LENBYTES: usize = 8;
pub const CHUNKBYTES: usize = 64;
const ONEPAD: u8 = 0x80;

fn rotr(x: Wrapping<u32>, n: usize) -> Wrapping<u32> {
    (x >> n) | (x << (WORDSIZE_BITS - n) )
}

pub fn ch(x: Wrapping<u32>, y: Wrapping<u32>, z: Wrapping<u32>) -> Wrapping<u32> {
    (x & y) ^ ((!x) & z)
}

pub fn maj(x: Wrapping<u32>, y: Wrapping<u32>, z: Wrapping<u32>) -> Wrapping<u32> {
    (x & y) ^ (x & z) ^ (y & z)
}

pub fn ls0(x: Wrapping<u32>) -> Wrapping<u32> {
    rotr(x, 2) ^ rotr(x,13) ^ rotr(x,22)
}

pub fn ls1(x: Wrapping<u32>) -> Wrapping<u32> {
    rotr(x,6) ^ rotr(x,11) ^ rotr(x,25)
}

fn s0(x: Wrapping<u32>) -> Wrapping<u32> {
    rotr(x,7) ^ rotr(x,18) ^ (x >> 3)
}

fn s1(x: Wrapping<u32>) -> Wrapping<u32> {
    rotr(x,17) ^ rotr(x,19) ^ (x >> 10)
}

pub fn padmessage(bytebuffer : &mut [u8], readbyte : usize, messagelength: u64) -> () {
    let bitsize: u64 = messagelength * 8;
    let mut pos: usize = readbyte ;
    bytebuffer[pos] = ONEPAD;
    pos+=1;
    for i in pos..(CHUNKBYTES-LENBYTES) {
        bytebuffer[i] = 0;
    }

    let mut v: u8;
    for i in 0..8 {
        v = ((bitsize >> i*8) & 0xff) as u8;
        bytebuffer[63-i] = v;
    }

}

pub fn message_schedule(wordbuffer: [Wrapping<u32>; 16]) -> [Wrapping<u32>; 64] {
    let mut ms: [Wrapping<u32>; 64] = [Wrapping(0); 64];
    ms[..16].clone_from_slice(&wordbuffer);
    for t in 16..64 {
        ms[t] = s1(ms[t-2]) + ms[t-7] + s0(ms[t-15]) + ms[t-16];
    }

    ms
}

pub fn bytestowords(bytebuffer: [u8; CHUNKBYTES]) -> [Wrapping<u32>; 16] {
    let mut wordbuffer: [Wrapping<u32>; 16] = [Wrapping(0);16];
    let mut v: Wrapping<u32>;
    for i in 0..16 {
        v=Wrapping(0);
        v += Wrapping(bytebuffer[4*i] as u32) << (3*8);
        v += Wrapping(bytebuffer[4*i+1] as u32) << (2*8);
        v += Wrapping(bytebuffer[4*i+2] as u32) << (1*8);
        v += Wrapping(bytebuffer[4*i+3] as u32);
        wordbuffer[i] = v;
    }

    wordbuffer

}
\$\endgroup\$
3
  • \$\begingroup\$ Check out -C target-cpu=native. Also, assuming your benchmark includes I/O, note that stdout is line buffered and must be locked on every call to print. See this helpful article. \$\endgroup\$
    – JayDepp
    Commented Jun 7, 2019 at 17:45
  • \$\begingroup\$ Also note that returning the array [Wrapping<u32>; 64] requires copying 4*64 = 256 bytes if not optimized away. You may prefer taking a &mut [Wrapping<u32>; 64] argument if the function is in a hot loop or otherwise determined to be taking a lot of time. \$\endgroup\$
    – JayDepp
    Commented Jun 7, 2019 at 17:56
  • \$\begingroup\$ The above code looks good from a design perspective IMHO. I'm a bit wondering why you decided to mix main and part of the function though. I'd first test against test vectors, then optimize (unroll loop, inline functions) and finally clean up. If you use vars from the standard or from another implementation, then please provide a reference to that standard in the code (visiting unanswered crypto questions). \$\endgroup\$ Commented Feb 12, 2020 at 12:16

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.