3
votes

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

2
Oops. I need to take a reference to the result of unsafe, but you get the idea : I do not want to mem::transmute a pointer. - Jeff Burdges
You should edit your own question instead of commenting to yourself. - Shepmaster
In fact, I must mem:transmute the reference since I need to pass the reference to sha.result(), but maybe I can find a safer way to do this. - Jeff Burdges

2 Answers

3
votes

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.

0
votes

I marked the accepted answer as best since it's safe, and led me to the clone_into_array answer here, but..

Another idea that improves the safety is to make a version of mem::transmute for references that checks the sizes of the referenced types, as opposed to just the pointers. It might look like :

#[inline]
unsafe fn transmute_ptr_mut<A,B>(v: &mut A) -> &mut B {
    debug_assert_eq!(core::mem::size_of(A),core::mem::size_of(B));
    core::mem::transmute::<&mut A,&mut B>(v)
}

I have raised an issue on the arrayref crate to discuss this, as arrayref might be a reasonable crate for it to live in.

Update : We've a new "best answer" by the arrayref crate developer :

let (a,b) = array_refs![&r,32,16];
(MessageKey(*a), LeafKey(*b))