1
votes

I'm trying to figure out how to add a field that is a cryptographic hash of other fields. I have:

pub struct Message {
    pub size: usize,
    pub id: MessageId,
    pub attribute: MessageAttribute,
}

I'd like to have something like:

pub struct Message {
    pub size: usize,
    pub id: MessageId,
    pub attribute: MessageAttribute,
    pub hash: MessageHash,
}

pub struct MessageHash(pub Vec<u8>);

I could create another struct and then compute the hash when setting up the struct:

pub struct HashedMessage {
    pub content: Message,
    pub hash: MessageHash,
}

pub fn message_hash(data: &Message) -> MessageHash {
    let mut hasher = DefaultHasher::new();
    data.hash(&mut hasher);
    MessageHash(hasher.finalize().to_vec())
}

let content = Message { /* ... */ };
let hash = hash_message(msg);
let msg = HashedMessage { content, hash };

This method introduces another struct, and I would have to change the codebase to use the new struct in place of the old one.

Another way I is to have a new method that receives each member of the struct, and then output the final struct with the hash field computed over the inputs. This seems reasonable. A possible option would be to set the hash to 0 in this method and then compute the hash using message_hash over the Message struct (the first one, with hash field embedded), and then assign it.

Is there another way? What would be an idiomatic, correct way to add a hash? These are messages that will be serialized and sent over the wire, so I can't have a method that will compute the hash every time.

1
And how the second snippet doesn't suffice you? If you care that this hash will be serialized together with the main content, then serde can be told to ignore this hash field - Alexey Larionov
Hopefully it's just for your example, but DefaultHasher is not a cryptographic hash. - Shepmaster
Regarding I would have to change the codebase to use the new struct in place of the old one, I'm not sure I agree -- if the codebase doesn't need the hash field, you can simply write content(&self) -> &Message and into_content(self) -> Message accessors (or implement Borrow and From, if that suits your use case) and continue to use Message where HashedMessage is not needed, using the wrapper only for the parts where a hash is necessary. (If the rest of the codebase does need a hashed message, obviously you'd have to change it anyway.) - trentcl
@Shepmaster Yes, this is just an example, I was lazy to look up the name of the Sha256Hasher :) - Nikola Knezevic
@trentcl That is because of the last sentence -- the message will be sent over the wire, thus, for the methods that send/receive the message, I need to have the hash field -- I can't recompute it whenever needed. - Nikola Knezevic

1 Answers

3
votes

You can use composition like this:

pub struct Message {
    pub size: usize,
    pub id: MessageId,
    pub attribute: MessageAttribute,
}

pub struct Hashed<M> {
    pub hash: MessageHash,
    pub message: M,
}

impl<M> Hashed<M> {
    fn new(message: M) -> Self {
        let hash = compute_hash(&message);
        Self {
            hash,
            message,
        }
    }
}

Then you can use either Message or Hashed<Message>.

If you have a Hashed<Message> but you need a Message, you can borrow that field. Going the other way, you'd have to create a new Hashed<Message> and compute the hash again.