Search code examples
rustunsafe

Ergonomics issues with fixed size byte arrays in Rust


Rust sadly cannot produce a fixed size array [u8; 16] with a fixed size slicing operator s[0..16]. It'll throw errors like "expected array of 16 elements, found slice".

I've some KDFs that output several keys in wrapper structs like

pub struct LeafKey([u8; 16]);
pub struct MessageKey([u8; 32]);

fn kdfLeaf(...) -> (MessageKey,LeafKey) {
    // let mut r: [u8; 32+16];
    let mut r: (MessageKey, LeafKey);
    debug_assert_eq!(mem::size_of_val(&r), 384/8);
    let mut sha = Sha3::sha3_384();
    sha.input(...);

    // sha.result(r);
    sha.result( 
      unsafe { mem::transmute::<&mut (MessageKey, LeafKey),&mut [u8;32+16]>(&r) } 
    );
    sha.reset();

    // (MessageKey(r[0..31]), LeafKey(r[32..47]))
    r
}

Is there a safer way to do this? We know mem::transmute will refuse to compile if the types do not have the same size, but that only checks that pointers have the same size here, so I added that debug_assert.

In fact, I'm not terribly worried about extra copies though since I'm running SHA3 here, but afaik rust offers no ergonomic way to copy amongst byte arrays.

Can I avoid writing (MessageKey, LeafKey) three times here? Is there a type alias for the return type of the current function? Is it safe to use _ in the mem::transmute given that I want the code to refuse to compile if the sizes do not match? Yes, I know I could make a type alias, but that seems silly.

As an aside, there is a longer discussion of s[0..16] not having type [u8; 16] here


Solution

  • There's the copy_from_slice method.

    fn main() {
        use std::default::Default;
    
        // Using 16+8 because Default isn't implemented
        // for [u8; 32+16] due to type explosion unfortunateness
        let b: [u8; 24] = Default::default();
        let mut c: [u8; 16] = Default::default();
        let mut d: [u8; 8] = Default::default();
    
        c.copy_from_slice(&b[..16])
        d.copy_from_slice(&b[16..16+8]);
    }
    

    Note, unfortunately copy_from_slice throws a runtime error if the slices are not the same length, so make sure you thoroughly test this yourself, or use the lengths of the other arrays to guard.

    Unfortunately, c.copy_from_slice(&b[..c.len()]) doesn't work because Rust thinks c is borrowed both immutably and mutably at the same time.