GithubHelp home page GithubHelp logo

Comments (10)

newpavlov avatar newpavlov commented on May 20, 2024

Thank you for highlighting this issue and welcome!

I am afraid Digest trait is not object safe, because it has new() static method. Even without it I think using boxed Digest will be quite unergonomic due to the necessity to fix output and block sizes. For now the following workaround can be used to do that you want:

trait ObjectSafeDigest {
    fn input(&mut self, data: &[u8]);

    fn output_size(&self) -> usize;

    fn output(self, buf: &mut [u8]) -> Result<(), InvalidLength>;
}

impl<T: Input + FixedOutput> ObjectSafeDigest for T {
    fn input(&mut self, data: &[u8]) {
        self.process(data);
    }

    fn output_size(&self) -> usize {
        <Self as FixedOutput>::OutputSize::to_usize()
    }

    fn output(self, buf: &mut [u8]) -> Result<(), InvalidLength> {
        if buf.len() != self.output_size() {
            Err(InvalidLength)
        } else {
            buf.copy_from_slice(&self.fixed_result());
            Ok(())
        }
    }
}

You may want to add reset functionality to output method to potentially remove unnecessary allocations. (output(&mut self) -> GenericArray<..> which will reset digest instance instead of consuming it will be introduced in digest v0.8)

Ideally we would like to have object safe solution in the digest crate, but I am not sure how it should look. Probably a separate trait like in the example?

from hashes.

tarcieri avatar tarcieri commented on May 20, 2024

Rather than having an object-safe "wrapper trait", perhaps Digest could be decomposed into a set of traits whose core is object-safe and also reusable across a wider set of cryptographic primitives? Something like:

trait IUF {
    /// Hax until we have const generics
    type OutputSize: Unsigned;

    /// Reset this primitive to its initial state
    fn init(&mut self);

    /// Add data (ala `input` in the current trait)
    fn update(&mut self, data: &[u8]);

    /// Finalize the result, outputting a digest and resetting the primitive to its initial state
    fn finish(&mut self) -> Output<Self::OutputSize>
}

From there you could have marker traits for various object-safe crypto primitives:

trait DigestIUF: IUF {}
trait MACIUF: IUF {}
trait SignatureIUF: IUF {}

And then you can build higher-level, non-object-safe traits on top of those:

trait Digest: DigestIUF + Default {
    /// Create new hasher instance
    fn new() -> Self {
        Self::default()
    }
}

from hashes.

newpavlov avatar newpavlov commented on May 20, 2024

I think having OutputSize in the object safe trait will kill ergonomics (with or without const generics), as users will have to fix it before boxing, i.e. they will have to use Box<IUF<OutputSize=64>> instead of just Box<IUF> which will be universal for all output sizes.

One possibility is to cover fixed size, variable size and XOFs by a single trait with the interface shown in the example earlier. If I am not missing something changing output_size method to return Some(usize) should be enough to cover XOFs.

from hashes.

tarcieri avatar tarcieri commented on May 20, 2024

I don't know any way around that... either things are specialized for a particular output size in which case you want that kind of constraint, or they're generic over it.

Perhaps this sort of thing is best discussed after const generics land and we can have a more tangible discussion of the ergonomics.

from hashes.

newpavlov avatar newpavlov commented on May 20, 2024

The only way around it which I can see is to use result(&mut self, buf: &mut [u8]) -> Result<(), InvalidLength> method. I don't think introduction of const generics will change much, as if we have method which returns data with its size dependent on associated constant we have to fix this constant before boxing (at least without alloca support). But we probably don't want to remove array based API as well, because it's often easier to use, thus the separate object safe traits looks like the more appealing approach.

from hashes.

newpavlov avatar newpavlov commented on May 20, 2024

I've added a DynDigest trait which will land in v0.8. Do you have any suggestions how it can be improved?

from hashes.

newpavlov avatar newpavlov commented on May 20, 2024

Fixed with digest v0.8.

from hashes.

burdges avatar burdges commented on May 20, 2024

I'll write out some nicer forms that ignore the generic array noise by assuming const generics have made associated types superior.

There are some object safe traits already like Reset and the existing

pub trait Input {
    fn input<B: AsRef<[u8]>>(&mut self, data: B);
    fn chain<B: AsRef<[u8]>>(mut self, data: B) -> Self where Self: Sized {
        self.input(data);
        self
    }
}

ExtendableOutput would be better like

pub use rand_core::RngCore;  // Why use anything else?
pub trait XoF {
    type Reader: RngCore;
    fn xof(self) -> Self::Reader where Self: Sized;
    fn xof_reset(&mut self) -> Self::Reader where Self: Sized+Reset;
    fn xof_boxed(self: Box<Self>) -> Box<RngCore>;
    fn xof_reset_boxed(&mut self) -> Box<RngCore> where Self: Reset {
        box self.xof_reset()
    }
}

along with specializations

default impl<D: XoF+Reset+Clone+Copy> XoF for D {
    fn xof_reset(&mut self) -> Self::Reader where Self: Sized+Reset {
        let xof = self.clone().xof();
        self.reset();
        xof
    }
}
default impl<D: XoF> XoF for D
where D: Sized, D::Output: Sized
{
    fn xof_boxed(self: Box<Self>) -> Box<RngCore> {
        if size_of::<Self::Reader>() != size_of::<Self>() {
            return box (*self).xof_reset();
        }
        let mut d = unsafe { ::std::mem::uninitialized() };
        ::std::mem::swap(self.deref_mut(),&mut d);
        let s: Box<Self::Reader> = unsafe { ::std::mem::transmute(self) }.into();
        let mut r = d.xor_result();
        ::std::mem::swap(s.deref_mut(),&mut r);
        s
    }
}

Also FixedOutput would be better like:

pub trait FixedOutput {
    type Output: AsRef<[u8]>
    fn result(self) -> Output;
    fn result_reset(&mut self) -> Output where Self: Sized+Reset;
    fn result_boxed(self: Box<Self>) -> Vec<[u8]>;
    fn result_reset_boxed(&mut self) -> Vec<[u8]> where Self: Reset;
}

again along with specializations

default impl<D: FixedOutput+Reset+Clone+Copy> FixedOutput for D {
    fn result_reset(&mut self) -> Output where Self: Sized+Reset {
        let r = self.clone().result();
        self.reset();
        r
    }
}
default impl<D: FixedOutput> FixedOutput for D 
where D: FixedOutput+Sized, D::Output: Sized
{
    fn result_reset_boxed(&mut self) -> Vec<[u8]> where Self: Reset {
        let v = Vec::with_capacity(size_of::<Self::Output>());
        v.extend_from_slice(self.result_reset().as_ref())
        v
    }
    fn result_boxed(self: Box<Self>) -> Vec<[u8]> {
        if size_of::<Self::Output>() > size_of::<Self>() {
            return (box (*self).result()).into()
        }
        let mut d = unsafe { ::std::mem::uninitialized() };
        ::std::mem::swap(self.deref_mut(),&mut d);
        let r = ;
        let v: Vec<u8> = unsafe { ::std::mem::transmute<_,Box<[u8; size_of::<Self>()]>>(self) }.into();
        v.clear();
        v.extend_from_slice(d.result().as_ref());
        v
    }
}

I've assumed Copy occasionally above only so that Clone definitely works via byte copy. We dislike digests being Copy though so either Copy should be replaced by some CanBeMadeCopy trait, or more likely removed and the constraints on clone documented.

We could eventually ditch the digest trait noise, including the obnoxious conflicting method names, with

pub trait Digest = Input + FixedOutput + Reset + Default + Clone;
pub trait DigestXoF = Input + XoF + Reset + Default + Clone;

As an aside, if std::default::Default ever became object safe, then Reset could become an extension trait, like

pub trait Reset: Default {
    fn reset(&mut self) {
        let mut d = Self::default();
        ::std::mem::swap(self,&mut d);
    }
}
default impl<D: Default> Reset for D { }

except afaik it does not really simplify anything above.

from hashes.

newpavlov avatar newpavlov commented on May 20, 2024

You are missing the fact that reset is not simply a re-creation of the instance using Default. For example to reset BlockBuffer you just need to set counter to 0, and in some cases like with the keyed BLAKE2 we simply can't use Default.

pub use rand_core::RngCore; // Why use anything else?

I don't see why we should depend on rand_core in this situation.

type Output: AsRef<[u8]>

IIUC you still will have to fix the type parameter for boxed object, so it will not change anything. And I don't think it makes sense to drop explicit length for FixedOutput.

As for some convenience methods like boxed results for Digest and FixedOutput, we could add them, but I think we should use Box<[u8]> here instead of the Vec<u8>.

Overall right now I think that DynDigest does its job good enough, and there is not much reason to replace it with more complicated approaches. Plus unfortunately until multi-trait trait objects (i.e. Box<Read + Write>) or trait aliases usable with trait objects will land on stable we can't use combination of smaller traits.

from hashes.

burdges avatar burdges commented on May 20, 2024

I do think RngCore is the trait you want here. In particular, it addresses the issue that some XoFs eventually stop, but that that almost never matters. And rand_core has no problematic dependencies afaik.

Agreed, you might not want to fix the type parameter. Oops! You might want

pub trait FixedOutput {
    const OUTPUT_SIZE: usize;
    fn result(self) -> [u8; OUTPUT_SIZE];
    fn result_reset(&mut self) -> [u8; OUTPUT_SIZE] where Self: Sized+Reset;
    fn result_boxed(self: Box<Self>) -> Box<[u8; OUTPUT_SIZE]>;
    fn result_reset_boxed(&mut self) -> Box<[u8; OUTPUT_SIZE]> where Self: Reset;
}

Assuming you need not fix OUTPUT_SIZE in this future const generic setting we're discussing, except.. I've made a questionable assumption here because then result_reset actually becomes object safe if OUTPUT_SIZE is specified, or maybe using VLAs. In other words, allowing non-fixed OUTPUT_SIZE requires modifying the trait object rules! We should hope const generics need not wait on resolving this sort of problem! :)

As an aside, we cannot replace the Copy with !Drop because RefCell is not !Drop but !Drop still helps more than Copy here. I do use Clone on digests regularly, but doing so is not quite perfect.

from hashes.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.