From 59064a6a1485ab3c1e175fa7d174cfd814caecef Mon Sep 17 00:00:00 2001 From: koushiro Date: Fri, 17 Sep 2021 18:27:42 +0800 Subject: [PATCH] fix: scale-codec for multihash 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 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 --- src/lib.rs | 3 +++ src/multihash.rs | 64 +++++++++++++++++++++++++++++++++++++++--------- 2 files changed, 55 insertions(+), 12 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 828f9ff0..50d0b190 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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; diff --git a/src/multihash.rs b/src/multihash.rs index 7bcc3830..9dbba3c3 100644 --- a/src/multihash.rs +++ b/src/multihash.rs @@ -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; @@ -186,7 +188,12 @@ impl parity_scale_codec::Encode for Multihash { 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]); } } @@ -198,11 +205,17 @@ impl parity_scale_codec::Decode for Multihash { fn decode( input: &mut DecIn, ) -> Result { + 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) }, }) @@ -216,7 +229,12 @@ impl parity_scale_codec::Encode for Multihash { 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]); } } @@ -228,11 +246,17 @@ impl parity_scale_codec::Decode for Multihash { fn decode( input: &mut DecIn, ) -> Result { + 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) }, }) @@ -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::::default(); - let bytes = mh.encode(); - let mh2: Multihash = Decode::decode(&mut &bytes[..]).unwrap(); - assert_eq!(mh, mh2); + let mh1 = Multihash::::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 = Decode::decode(&mut &mh1_bytes[..]).unwrap(); + assert_eq!(mh1, mh2); + + let mh3: Multihash = 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 = Decode::decode(&mut &mh3_bytes[..]).unwrap(); + assert_eq!(mh3, mh4); + + assert_eq!(mh1_bytes, mh3_bytes); } #[test]