Skip to content

Commit

Permalink
fix: scale-codec for multihash
Browse files Browse the repository at this point in the history
BREAKING CHANGE: Write the digest of multihash directly to dest when encoding
multihash, since we have known the size of digest.

We do not choose to encode &[u8] directly, because it will add extra bytes
(the compact length of digest) at the beinging of encoding result.
For a valid multihash, the length of digest must equal to `size` field of multihash.
Therefore, we can only read raw bytes whose length is equal to `size` when decoding.

And the digest of Multihash<U64> is of type [u8; 64], taking Code::Sha2_256 as an example,
if the digest is treated as &[u8] when encoding, useless data will be added to
the encoding result.

Signed-off-by: koushiro <koushiro.cqx@gmail.com>
  • Loading branch information
koushiro authored and vmx committed Sep 22, 2021
1 parent 4a895f7 commit 59064a6
Show file tree
Hide file tree
Showing 2 changed files with 55 additions and 12 deletions.
3 changes: 3 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@
#![deny(missing_docs)]
#![cfg_attr(not(feature = "std"), no_std)]

#[cfg(not(feature = "std"))]
extern crate alloc;

#[cfg(any(test, feature = "arb"))]
mod arb;
mod error;
Expand Down
64 changes: 52 additions & 12 deletions src/multihash.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use crate::hasher::{Digest, Size};
use crate::Error;
#[cfg(all(not(feature = "std"), feature = "scale-codec"))]
use alloc::vec;
use core::convert::TryFrom;
#[cfg(feature = "std")]
use core::convert::TryInto;
Expand Down Expand Up @@ -186,7 +188,12 @@ impl parity_scale_codec::Encode for Multihash<crate::U32> {
digest.copy_from_slice(&self.digest);
self.code.encode_to(dest);
self.size.encode_to(dest);
digest.encode_to(dest);
// **NOTE** We write the digest directly to dest, since we have known the size of digest.
//
// We do not choose to encode &[u8] directly, because it will add extra bytes (the compact length of digest).
// For a valid multihash, the length of digest must equal to `size`.
// Therefore, we can only read raw bytes whose length is equal to `size` when decoding.
dest.write(&digest[..self.size as usize]);
}
}

Expand All @@ -198,11 +205,17 @@ impl parity_scale_codec::Decode for Multihash<crate::U32> {
fn decode<DecIn: parity_scale_codec::Input>(
input: &mut DecIn,
) -> Result<Self, parity_scale_codec::Error> {
let code = parity_scale_codec::Decode::decode(input)?;
let size = parity_scale_codec::Decode::decode(input)?;
// For a valid multihash, the length of digest must equal to the size.
let mut buf = vec![0u8; size as usize];
input.read(&mut buf)?;
Ok(Multihash {
code: parity_scale_codec::Decode::decode(input)?,
size: parity_scale_codec::Decode::decode(input)?,
code,
size,
digest: {
let digest = <[u8; 32]>::decode(input)?;
let mut digest = [0u8; 32];
digest[..size as usize].copy_from_slice(&buf);
GenericArray::clone_from_slice(&digest)
},
})
Expand All @@ -216,7 +229,12 @@ impl parity_scale_codec::Encode for Multihash<crate::U64> {
digest.copy_from_slice(&self.digest);
self.code.encode_to(dest);
self.size.encode_to(dest);
digest.encode_to(dest);
// **NOTE** We write the digest directly to dest, since we have known the size of digest.
//
// We do not choose to encode &[u8] directly, because it will add extra bytes (the compact length of digest).
// For a valid multihash, the length of digest must equal to `size`.
// Therefore, we can only read raw bytes whose length is equal to `size` when decoding.
dest.write(&digest[..self.size as usize]);
}
}

Expand All @@ -228,11 +246,17 @@ impl parity_scale_codec::Decode for Multihash<crate::U64> {
fn decode<DecIn: parity_scale_codec::Input>(
input: &mut DecIn,
) -> Result<Self, parity_scale_codec::Error> {
let code = parity_scale_codec::Decode::decode(input)?;
let size = parity_scale_codec::Decode::decode(input)?;
// For a valid multihash, the length of digest must equal to the size.
let mut buf = vec![0u8; size as usize];
input.read(&mut buf)?;
Ok(Multihash {
code: parity_scale_codec::Decode::decode(input)?,
size: parity_scale_codec::Decode::decode(input)?,
code,
size,
digest: {
let digest = <[u8; 64]>::decode(input)?;
let mut digest = [0u8; 64];
digest[..size as usize].copy_from_slice(&buf);
GenericArray::clone_from_slice(&digest)
},
})
Expand Down Expand Up @@ -302,12 +326,28 @@ mod tests {
#[test]
#[cfg(feature = "scale-codec")]
fn test_scale() {
use crate::{Hasher, Sha2_256};
use parity_scale_codec::{Decode, Encode};

let mh = Multihash::<crate::U32>::default();
let bytes = mh.encode();
let mh2: Multihash<crate::U32> = Decode::decode(&mut &bytes[..]).unwrap();
assert_eq!(mh, mh2);
let mh1 = Multihash::<crate::U32>::wrap(
Code::Sha2_256.into(),
Sha2_256::digest(b"hello world").as_ref(),
)
.unwrap();
// println!("mh1: code = {}, size = {}, digest = {:?}", mh1.code(), mh1.size(), mh1.digest());
let mh1_bytes = mh1.encode();
// println!("Multihash<32>: {}", hex::encode(&mh1_bytes));
let mh2: Multihash<crate::U32> = Decode::decode(&mut &mh1_bytes[..]).unwrap();
assert_eq!(mh1, mh2);

let mh3: Multihash<crate::U64> = Code::Sha2_256.digest(b"hello world");
// println!("mh3: code = {}, size = {}, digest = {:?}", mh3.code(), mh3.size(), mh3.digest());
let mh3_bytes = mh3.encode();
// println!("Multihash<64>: {}", hex::encode(&mh3_bytes));
let mh4: Multihash<crate::U64> = Decode::decode(&mut &mh3_bytes[..]).unwrap();
assert_eq!(mh3, mh4);

assert_eq!(mh1_bytes, mh3_bytes);
}

#[test]
Expand Down

0 comments on commit 59064a6

Please sign in to comment.