Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

crypto-common: add SerializableState trait #1078

Closed
wants to merge 0 commits into from

Conversation

rpiasetskyi
Copy link
Contributor

This trait is used for saving internal state of the hash core and
restoring it later.

The original issue was discussed here: RustCrypto/hashes#310

@tarcieri tarcieri requested review from newpavlov and tarcieri August 10, 2022 21:55
@@ -117,3 +117,15 @@ pub enum TruncSide {
/// Truncate right side, i.e. `&out[m..]`.
Right,
}

/// Core trait for saving internal state of the hash core and restoring it later.
pub trait HashCoreInternalState {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd suggest using Rust's built-in conversion traits rather than defining special methods like internal_state and from_internal_state:

Suggested change
pub trait HashCoreInternalState {
pub trait HashCoreInternalState: TryFrom<Self::InternalState, Error = Error> + Into<Self::InternalState> {

@tarcieri
Copy link
Member

I think the core notion here is a "serializable state" rather than an "internal state"

/// Core trait for saving internal state of the hash core and restoring it later.
pub trait HashCoreInternalState {
/// Internal state of the hash core.
type InternalState;
Copy link
Member

@tarcieri tarcieri Aug 10, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It'd be good to have some bounds here to enable generic code:

Suggested change
type InternalState;
type InternalState: TryFrom<&[u8]> + AsRef<[u8]>;

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the internal state consists of several objects, is it possible to implement TryFrom<&[u8]> + AsRef<[u8]>: e.g. internal state of SHA-2 hashes consists of the intermediate hash value (array of u32/u64) and the number of consumed blocks (number u64/u128).

Copy link
Member

@tarcieri tarcieri Aug 10, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The use case of this API is serialization, and serialization needs to operate in terms of bytestrings.

If you think there's a use case beyond serialization, can you explain how that use case isn't satisfied by simply cloning an existing digest instance?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, the use case is serialization only.
Should I then create a byte array and serialize the internal state on my own?
E.g. for Sha256VarCore, I need to allocate an array, copy the intermediate hash value ([u32; STATE_LEN]), and the number of consumed blocks (u64).
Or is it better to return an object that consists of the intermediate hash value and the number of consumed blocks, and this object is Serializable (serde)? But this object can not implement AsRef<[u8]> because it consists of a byte array and number.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It depends if the state can always be fixed-width or not. If it can, we should probably use an associated generic_array::ArrayLength<u8> instead.

I think it's a pretty reasonable assumption that most digests could support a fixed-width serializable state.

Copy link
Member

@newpavlov newpavlov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that it's worth to introduce trait which outputs generic array. Something like this:

pub type SerializedState<T> = GenericArray<u8, <T as SerializableState>::SerializedStateSize>;

pub trait SerializableState {
    type SerializedStateSize: ArrayLength<u8>;
    
    fn serialize(&self) -> SerializedState<Self>;
    fn deserialize(buf: &SerializedState<Self>) -> Self;
}

Also do not forget to add warning that serialized state may contain sensitive information, e.g. in the hashes case it can contain parts of unprocessed message.

The trait may go to the crypto-common crate since it may be useful for other algorithms as well.

@rpiasetskyi rpiasetskyi force-pushed the master branch 2 times, most recently from fc5c8f7 to 45e5ef5 Compare August 19, 2022 12:00
@rpiasetskyi
Copy link
Contributor Author

Sorry, missed the last comment. I will move the trait to crypto-common and use serialize/deserialize methods.

Should deserialize method return Result (if the buffer contains invalid values)?

@rpiasetskyi
Copy link
Contributor Author

The versions of the crates have to be updated: should they be 0.1.7/0.10.4 or 0.2.0/0.11.0?

Right now crypto-common is 0.1.6 and digest is 0.10.3.

Copy link
Member

@newpavlov newpavlov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should deserialize method return Result (if the buffer contains invalid values)?

Yes, I forgot to do it in my snippet. I think we can define the error type in crypto-common (i.e. no need to make it generic).

The versions of the crates have to be updated: should they be 0.1.7/0.10.4 or 0.2.0/0.11.0?

The changes are backwards-compatible, so patch releases should be fine.

digest/src/core_api/ct_variable.rs Outdated Show resolved Hide resolved
crypto-common/src/lib.rs Outdated Show resolved Hide resolved
@rpiasetskyi
Copy link
Contributor Author

Do not forget to also implement it for CoreWrapper and ideally for RtVariableCoreWrapper.

There is a small issue with RtVariableCoreWrapper: it has a member that has usize type.

It is needed for SerializedStateSize definition.
It should be something like:

type SerializedStateSize =  Sum<Sum<Add1<T::SerializedStateSize>, UsizeSize>, T::BlockSize>;

Is there any known UsizeSize definition?

Or should it be implemented via cfg?

#[cfg(target_pointer_width = "16")]
type UsizeSize = U2;
#[cfg(target_pointer_width = "32")]
type UsizeSize = U4;
#[cfg(target_pointer_width = "64")]
type UsizeSize = U8;

What should be the best place to put it In this case?

@tarcieri
Copy link
Member

The serialized state needs to be consistent regardless of the target pointer width.

You should pick a sensible on-the-wire representation (u32 or u64) and use a try_from conversion when deserializing it.

@newpavlov
Copy link
Member

I think we can simply transform output_size to u8 using TryInto and unwrap. I am not aware about variable hashes which support output size bigger than 255 bytes (XOF does not count). In future versions it could be worth to replace usize with u8.

@rpiasetskyi rpiasetskyi changed the title digest: add HashCoreInternalState trait digest: add SerializableState trait Sep 1, 2022
@newpavlov newpavlov changed the title digest: add SerializableState trait crypto-common: add SerializableState trait Sep 2, 2022

fn serialize(&self) -> SerializedState<Self> {
self.inner.serialize()
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As a sanity check it could be worth to add into serialized state output size (the OutSize type parameter) and return an error during deserialization if it does not match.

Copy link
Member

@newpavlov newpavlov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could use the concat method to slightly improve the code, though it could force you to make SerializedStateSize less... pretty. So it's quite optional.

<T::SerializedStateSize as Add<U9>>::Output: Add<T::BlockSize>,
<<T::SerializedStateSize as Add<U9>>::Output as Add<T::BlockSize>>::Output: ArrayLength<u8>,
{
type SerializedStateSize = Sum<Sum<T::SerializedStateSize, U9>, T::BlockSize>;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you forgot to change U9 to U2.


Ok(Self {
core: T::deserialize(serialized_core)?,
buffer: BlockBuffer::new(&block_buffer[..pos]),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that if the buffer is "eager" and pos is equal to block size, new will panic. I guess the simplest solution will be to add try_new method to BlockBuffer.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


Ok(Self {
core: T::deserialize(serialized_core)?,
buffer: BlockBuffer::new(block_buffer),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as in the previous comment.

@rpiasetskyi
Copy link
Contributor Author

rpiasetskyi commented Sep 3, 2022

Converted to draft to avoid emails about failed tests.

UPD: Did not help :(

@rpiasetskyi rpiasetskyi marked this pull request as ready for review September 3, 2022 21:01
@rpiasetskyi rpiasetskyi force-pushed the master branch 2 times, most recently from 793f9f2 to bc9dd77 Compare September 4, 2022 21:24
@rpiasetskyi
Copy link
Contributor Author

With the introduction of the AssociatedOid trait and new generic parameter for CtVariableCoreWrapper, is it needed to add it to serialized state?

@tarcieri
Copy link
Member

No, those are type-level properties which are identical regardless of the state.

@rpiasetskyi
Copy link
Contributor Author

Does it make sense to implement SerializableState for some commonly used types?

impl SerializableState for u8 {
    type SerializedStateSize = U1;

    fn serialize(&self) -> SerializedState<Self> {
        [*self].into()
    }

    fn deserialize(
        serialized_state: &SerializedState<Self>,
    ) -> Result<Self, DeserializeStateError> {
        Ok(serialized_state[0])
    }
}

impl SerializableState for u64 {
    type SerializedStateSize = U8;

    fn serialize(&self) -> SerializedState<Self> {
        self.to_le_bytes().into()
    }

    fn deserialize(
        serialized_state: &SerializedState<Self>,
    ) -> Result<Self, DeserializeStateError> {
        Ok(u64::from_le_bytes((*serialized_state).into()))
    }
}

impl SerializableState for [u64; 16] {
    type SerializedStateSize = U128;

    fn serialize(&self) -> SerializedState<Self> {
        let mut serialized_state = SerializedState::<Self>::default();
        for (val, chunk) in self.iter().zip(serialized_state.chunks_exact_mut(8)) {
            chunk.copy_from_slice(&val.to_le_bytes());
        }

        serialized_state
    }

    fn deserialize(
        serialized_state: &SerializedState<Self>,
    ) -> Result<Self, DeserializeStateError> {
        let mut array = Self::default();

        for (val, chunk) in array.iter_mut().zip(serialized_state.chunks_exact(8)) {
            *val = u64::from_le_bytes(chunk.try_into().unwrap());
        }

        Ok(array)
    }
}

It can be implemented in a similar way to this https://github.com/fizyk20/generic-array/blob/master/src/impls.rs#L183.

This can move the for loop (array serialization/deserialization) in one place and make the code a little bit simpler.

type State = [u64; 16];

impl SerializableState for GroestlLongVarCore {
    type SerializedStateSize = Sum<
        <u64 as SerializableState>::SerializedStateSize,
        <State as SerializableState>::SerializedStateSize,
    >;

    fn serialize(&self) -> SerializedState<Self> {
        self.state.serialize().concat(self.blocks_len.serialize())
    }

    fn deserialize(
        serialized_state: &SerializedState<Self>,
    ) -> Result<Self, DeserializeStateError> {
        let (serialized_state, serialized_blocks_len) =
            Split::<_, <State as SerializableState>::SerializedStateSize>::split(serialized_state);

        Ok(Self {
            state: State::deserialize(serialized_state)?,
            blocks_len: u64::deserialize(serialized_blocks_len)?,
        })
    }
}

And it might be possible to implement #[derive(SerializableState)] for types that do not need to check anything.

What do you think about 3447449?
If this is okay, I will also add #[derive(SerializableState)].

@rpiasetskyi rpiasetskyi force-pushed the master branch 2 times, most recently from 3611c22 to 2ad3d70 Compare September 26, 2022 11:50
@rpiasetskyi
Copy link
Contributor Author

Could you please have a look at afc029e ? This is the implementation of #[derive(SerializableState)] macro. It makes it very easy to implement SerializableState trait for cores (e.g. for fsb RustCrypto/hashes@589b43b).

The current implementation doesn't support generics for now.

Also, crypto_common should be imported in the file that makes derivation.

@waynr
Copy link

waynr commented Oct 10, 2023

👋 any chance of getting this merged any time in the near future?

@tarcieri
Copy link
Member

@waynr it needs a rebase, at the very least

@waynr
Copy link

waynr commented Oct 11, 2023

@tarcieri I'm attempting to rebase but running into compile errors that are pretty much totally incomprehensible to me. This repo is like a firehose of generic types and my puny little brain only has capacity for a trickle.

@tarcieri
Copy link
Member

I think @rpiasetskyi said he would take a look

@waynr
Copy link

waynr commented Oct 11, 2023

I figured out that the problem had to do with updating the crypto-common dependency in the digest crate and opened a PR to show the same problem on master branch: #1359

@waynr
Copy link

waynr commented Oct 11, 2023

Just a quick note - it looks like the issue of the local dependency on crypto-common being broken in master branch for the digest crate is fixed in #1358 so this PR might need to wait for that before being rebased.

@rpiasetskyi
Copy link
Contributor Author

I am doing a rebase (and migration to hybrid_array) using #1358.

@rpiasetskyi
Copy link
Contributor Author

@tarcieri Sorry, I was expecting it to update the PR but not to merge it :(

@rpiasetskyi
Copy link
Contributor Author

Reopen.

@tarcieri
Copy link
Member

I think you pushed 0 commits which auto-closed the PR? Regardless I can't reopen it... I think you'd need to push some new commits

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants