Comments (10)
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.
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.
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.
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.
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.
I've added a DynDigest
trait which will land in v0.8
. Do you have any suggestions how it can be improved?
from hashes.
Fixed with digest v0.8
.
from hashes.
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.
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.
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)
- comparing after dereferencing or unsafely indexing, which is faster? HOT 1
- Blake2 parameters cannot be used without a key HOT 3
- use keccak crate in k12 HOT 2
- streebog: Use const fn to generate `SHUFFLED_LIN_TABLE`
- fix(?): some `extern` functions couldn't be found HOT 2
- `sha2` fails to build in Windows on an ARM device HOT 2
- blake2: MAC variants should not be constructable with an empty key?
- sha2: Interested in a way to save/restore internal state HOT 1
- Strange quirk in SHA256 impl HOT 1
- "no such associated item" in IDE but build succeeds HOT 3
- jh: remove dependency on `ppv-lite86`
- Sha256::new() is always marked as an error in the vscode HOT 1
- can't build `[email protected]` HOT 1
- soft-sha512 code size seems unreasonably high on thumbv7em HOT 1
- Broken documentation: What on earth is "GenericArray" HOT 8
- sha256 has very wildly varying performance compared to ring between computers (same binary) HOT 2
- sha2: aarch64 acceleration broken on master
- sha2: use ARM intrinsics rather than ASM HOT 1
- Finalising a sha256 has into an [u8; 32] HOT 9
- When are asm and intrinsics worth it? HOT 1
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
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.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from hashes.