diff --git a/eth2/utils/ssz/src/decode.rs b/eth2/utils/ssz/src/decode.rs index 480f39479..410ff69c2 100644 --- a/eth2/utils/ssz/src/decode.rs +++ b/eth2/utils/ssz/src/decode.rs @@ -173,209 +173,3 @@ fn decode_offset(bytes: &[u8]) -> Result { Ok(u32::from_le_bytes(array) as usize) } } - -/* - -/// Decode the given bytes for the given type -/// -/// The single ssz encoded value/container/list will be decoded as the given type, -/// by recursively calling `ssz_decode`. -/// Check on totality for underflowing the length of bytes and overflow checks done per container -pub fn decode(ssz_bytes: &[u8]) -> Result<(T), DecodeError> -where - T: Decodable, -{ - let (decoded, i): (T, usize) = match T::ssz_decode(ssz_bytes, 0) { - Err(e) => return Err(e), - Ok(v) => v, - }; - - if i < ssz_bytes.len() { - return Err(DecodeError::TooLong); - } - - Ok(decoded) -} - -/// Decode a vector (list) of encoded bytes. -/// -/// Each element in the list will be decoded and placed into the vector. -pub fn decode_ssz_list(ssz_bytes: &[u8], index: usize) -> Result<(Vec, usize), DecodeError> -where - T: Decodable, -{ - if index + LENGTH_BYTES > ssz_bytes.len() { - return Err(DecodeError::TooShort); - }; - - // get the length - let serialized_length = match decode_length(ssz_bytes, index, LENGTH_BYTES) { - Err(v) => return Err(v), - Ok(v) => v, - }; - - let final_len: usize = index + LENGTH_BYTES + serialized_length; - - if final_len > ssz_bytes.len() { - return Err(DecodeError::TooShort); - }; - - let mut tmp_index = index + LENGTH_BYTES; - let mut res_vec: Vec = Vec::new(); - - while tmp_index < final_len { - match T::ssz_decode(ssz_bytes, tmp_index) { - Err(v) => return Err(v), - Ok(v) => { - tmp_index = v.1; - res_vec.push(v.0); - } - }; - } - - Ok((res_vec, final_len)) -} - -/// Given some number of bytes, interpret the first four -/// bytes as a 32-bit little-endian integer and return the -/// result. -pub fn decode_length( - bytes: &[u8], - index: usize, - length_bytes: usize, -) -> Result { - if bytes.len() < index + length_bytes { - return Err(DecodeError::TooShort); - }; - let mut len: usize = 0; - for (i, byte) in bytes - .iter() - .enumerate() - .take(index + length_bytes) - .skip(index) - { - let offset = (i - index) * 8; - len |= (*byte as usize) << offset; - } - Ok(len) -} - -#[cfg(test)] -mod tests { - use super::super::encode::*; - use super::*; - - #[test] - fn test_ssz_decode_length() { - let decoded = decode_length(&vec![1, 0, 0, 0], 0, LENGTH_BYTES); - assert_eq!(decoded.unwrap(), 1); - - let decoded = decode_length(&vec![0, 1, 0, 0], 0, LENGTH_BYTES); - assert_eq!(decoded.unwrap(), 256); - - let decoded = decode_length(&vec![255, 1, 0, 0], 0, LENGTH_BYTES); - assert_eq!(decoded.unwrap(), 511); - - let decoded = decode_length(&vec![255, 255, 255, 255], 0, LENGTH_BYTES); - assert_eq!(decoded.unwrap(), 4294967295); - } - - #[test] - fn test_encode_decode_length() { - let params: Vec = vec![ - 0, - 1, - 2, - 3, - 7, - 8, - 16, - 2 ^ 8, - 2 ^ 8 + 1, - 2 ^ 16, - 2 ^ 16 + 1, - 2 ^ 24, - 2 ^ 24 + 1, - 2 ^ 32, - ]; - for i in params { - let decoded = decode_length(&encode_length(i, LENGTH_BYTES), 0, LENGTH_BYTES).unwrap(); - assert_eq!(i, decoded); - } - } - - #[test] - fn test_encode_decode_ssz_list() { - let test_vec: Vec = vec![256; 12]; - let mut stream = SszStream::new(); - stream.append_vec(&test_vec); - let ssz = stream.drain(); - - // u16 - let decoded: (Vec, usize) = decode_ssz_list(&ssz, 0).unwrap(); - - assert_eq!(decoded.0, test_vec); - assert_eq!(decoded.1, LENGTH_BYTES + (12 * 2)); - } - - #[test] - fn test_decode_ssz_list() { - // u16 - let v: Vec = vec![10, 10, 10, 10]; - let decoded: (Vec, usize) = - decode_ssz_list(&vec![8, 0, 0, 0, 10, 0, 10, 0, 10, 0, 10, 0], 0).unwrap(); - - assert_eq!(decoded.0, v); - assert_eq!(decoded.1, LENGTH_BYTES + (4 * 2)); - - // u32 - let v: Vec = vec![10, 10, 10, 10]; - let decoded: (Vec, usize) = decode_ssz_list( - &vec![ - 16, 0, 0, 0, 10, 0, 0, 0, 10, 0, 0, 0, 10, 0, 0, 0, 10, 0, 0, 00, - ], - 0, - ) - .unwrap(); - assert_eq!(decoded.0, v); - assert_eq!(decoded.1, 20); - - // u64 - let v: Vec = vec![10, 10, 10, 10]; - let decoded: (Vec, usize) = decode_ssz_list( - &vec![ - 32, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 0, 0, - 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, - ], - 0, - ) - .unwrap(); - assert_eq!(decoded.0, v); - assert_eq!(decoded.1, LENGTH_BYTES + (8 * 4)); - - // Check that it can accept index - let v: Vec = vec![15, 15, 15, 15]; - let offset = 10; - let decoded: (Vec, usize) = decode_ssz_list( - &vec![ - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 32, 0, 0, 0, 15, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 0, - 0, 0, 0, 15, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 0, 0, 0, 0, - ], - offset, - ) - .unwrap(); - assert_eq!(decoded.0, v); - assert_eq!(decoded.1, offset + LENGTH_BYTES + (8 * 4)); - - // Check that length > bytes throws error - let decoded: Result<(Vec, usize), DecodeError> = - decode_ssz_list(&vec![32, 0, 0, 0, 15, 0, 0, 0, 0, 0, 0, 0], 0); - assert_eq!(decoded, Err(DecodeError::TooShort)); - - // Check that incorrect index throws error - let decoded: Result<(Vec, usize), DecodeError> = - decode_ssz_list(&vec![15, 0, 0, 0, 0, 0, 0, 0], 16); - assert_eq!(decoded, Err(DecodeError::TooShort)); - } -} -*/ diff --git a/eth2/utils/ssz/src/encode.rs b/eth2/utils/ssz/src/encode.rs index 9e3af65c4..acc15bed2 100644 --- a/eth2/utils/ssz/src/encode.rs +++ b/eth2/utils/ssz/src/encode.rs @@ -64,197 +64,62 @@ impl<'a> SszEncoder<'a> { } } -/* -pub struct VariableLengths { - pub fixed_bytes_position: usize, - pub variable_bytes_length: usize, -} - -/// Provides a buffer for appending SSZ values. -#[derive(Default)] -pub struct SszStream { - fixed_bytes: Vec, - variable_bytes: Vec, - variable_lengths: Vec, -} - -impl SszStream { - /// Create a new, empty stream for writing SSZ values. - pub fn new() -> Self { - SszStream { - fixed_bytes: vec![], - variable_bytes: vec![], - variable_lengths: vec![], - } - } - - /* - /// Append some item to the stream. - pub fn append(&mut self, item: &T) { - let mut bytes = item.as_ssz_bytes(); - - if T::is_ssz_fixed_len() { - self.app - self.fixed_bytes.append(&mut bytes); - } else { - self.variable_lengths.push(VariableLengths { - fixed_bytes_position: self.fixed_bytes.len(), - variable_bytes_length: bytes.len(), - }); - - self.fixed_bytes - .append(&mut vec![0; BYTES_PER_LENGTH_OFFSET]); - self.variable_bytes.append(&mut bytes); - } - } - */ - pub fn reserve(&mut self, additional: usize) { - if T::is_ssz_fixed_len() { - self.fixed_bytes.reserve(additional * T::ssz_fixed_len()); - } else { - self.fixed_bytes - .reserve(additional * BYTES_PER_LENGTH_OFFSET); - self.variable_lengths.reserve(additional); - } - } - - pub fn append_fixed_bytes(&mut self, bytes: &[u8]) { - self.fixed_bytes.extend_from_slice(bytes) - } - - pub fn append_variable_bytes(&mut self, bytes: &[u8]) { - self.variable_lengths.push(VariableLengths { - fixed_bytes_position: self.fixed_bytes.len(), - variable_bytes_length: bytes.len(), - }); - - self.fixed_bytes - .append(&mut vec![0; BYTES_PER_LENGTH_OFFSET]); - - self.variable_bytes.extend_from_slice(bytes); - } - - /// Update the offsets (if any) in the fixed-length bytes to correctly point to the values in - /// the variable length part. - pub fn apply_offsets(&mut self) { - let mut running_offset = self.fixed_bytes.len(); - - for v in &self.variable_lengths { - let offset = running_offset; - running_offset += v.variable_bytes_length; - - self.fixed_bytes.splice( - v.fixed_bytes_position..v.fixed_bytes_position + BYTES_PER_LENGTH_OFFSET, - encode_length(offset), - ); - } - } - - /// Append the variable-length bytes to the fixed-length bytes and return the result. - pub fn drain(mut self) -> Vec { - self.apply_offsets(); - - self.fixed_bytes.append(&mut self.variable_bytes); - - self.fixed_bytes - } -} -*/ - /// Encode `len` as a little-endian byte vec of `BYTES_PER_LENGTH_OFFSET` length. /// /// If `len` is larger than `2 ^ BYTES_PER_LENGTH_OFFSET`, a `debug_assert` is raised. pub fn encode_length(len: usize) -> Vec { + // Note: it is possible for `len` to be larger than what can be encoded in + // `BYTES_PER_LENGTH_OFFSET` bytes, triggering this debug assertion. + // + // These are the alternatives to using a `debug_assert` here: + // + // 1. Use `assert`. + // 2. Push an error to the caller (e.g., `Option` or `Result`). + // 3. Ignore it completely. + // + // I have avoided (1) because it's basically a choice between "produce invalid SSZ" or "kill + // the entire program". I figure it may be possible for an attacker to trigger this assert and + // take the program down -- I think producing invalid SSZ is a better option than this. + // + // I have avoided (2) because this error will need to be propagated upstream, making encoding a + // function which may fail. I don't think this is ergonomic and the upsides don't outweigh the + // downsides. + // + // I figure a `debug_assertion` is better than (3) as it will give us a change to detect the + // error during testing. + // + // If you have a different opinion, feel free to start an issue and tag @paulhauner. debug_assert!(len <= MAX_LENGTH_VALUE); len.to_le_bytes()[0..BYTES_PER_LENGTH_OFFSET].to_vec() } -/* #[cfg(test)] mod tests { use super::*; #[test] - #[should_panic] - fn test_encode_length_0_bytes_panic() { - encode_length(0, 0); - } + fn test_encode_length() { + assert_eq!(encode_length(0), vec![0; 4]); + + assert_eq!(encode_length(1), vec![1, 0, 0, 0]); - #[test] - fn test_encode_length_4_bytes() { - assert_eq!(encode_length(0, LENGTH_BYTES), vec![0; 4]); - assert_eq!(encode_length(1, LENGTH_BYTES), vec![1, 0, 0, 0]); - assert_eq!(encode_length(255, LENGTH_BYTES), vec![255, 0, 0, 0]); - assert_eq!(encode_length(256, LENGTH_BYTES), vec![0, 1, 0, 0]); assert_eq!( - encode_length(4294967295, LENGTH_BYTES), // 2^(3*8) - 1 - vec![255, 255, 255, 255] + encode_length(MAX_LENGTH_VALUE), + vec![255; BYTES_PER_LENGTH_OFFSET] ); } - #[test] - fn test_encode_lower_length() { - assert_eq!(encode_length(0, LENGTH_BYTES - 2), vec![0; 2]); - assert_eq!(encode_length(1, LENGTH_BYTES - 2), vec![1, 0]); - } - - #[test] - fn test_encode_higher_length() { - assert_eq!(encode_length(0, LENGTH_BYTES + 2), vec![0; 6]); - assert_eq!(encode_length(1, LENGTH_BYTES + 2), vec![1, 0, 0, 0, 0, 0]); - } - #[test] #[should_panic] - fn test_encode_length_4_bytes_panic() { - encode_length(4294967296, LENGTH_BYTES); // 2^(3*8) + #[cfg(debug_assertions)] + fn test_encode_length_above_max_debug_panics() { + encode_length(MAX_LENGTH_VALUE + 1); } #[test] - fn test_encode_list() { - let test_vec: Vec = vec![256; 12]; - let mut stream = SszStream::new(); - stream.append_vec(&test_vec); - let ssz = stream.drain(); - - assert_eq!(ssz.len(), LENGTH_BYTES + (12 * 2)); - assert_eq!(ssz[0..4], *vec![24, 0, 0, 0]); - assert_eq!(ssz[4..6], *vec![0, 1]); - } - - #[test] - fn test_encode_mixed_prefixed() { - let test_vec: Vec = vec![100, 200]; - let test_value: u8 = 5; - - let mut stream = SszStream::new(); - stream.append_vec(&test_vec); - stream.append(&test_value); - let ssz = stream.drain(); - - assert_eq!(ssz.len(), LENGTH_BYTES + (2 * 2) + 1); - assert_eq!(ssz[0..4], *vec![4, 0, 0, 0]); - assert_eq!(ssz[4..6], *vec![100, 0]); - assert_eq!(ssz[6..8], *vec![200, 0]); - assert_eq!(ssz[8], 5); - } - - #[test] - fn test_encode_mixed_postfixed() { - let test_value: u8 = 5; - let test_vec: Vec = vec![100, 200]; - - let mut stream = SszStream::new(); - stream.append(&test_value); - stream.append_vec(&test_vec); - let ssz = stream.drain(); - - assert_eq!(ssz.len(), 1 + LENGTH_BYTES + (2 * 2)); - assert_eq!(ssz[0], 5); - assert_eq!(ssz[1..5], *vec![4, 0, 0, 0]); - assert_eq!(ssz[5..7], *vec![100, 0]); - assert_eq!(ssz[7..9], *vec![200, 0]); + #[cfg(not(debug_assertions))] + fn test_encode_length_above_max_not_debug_does_not_panic() { + assert_eq!(encode_length(MAX_LENGTH_VALUE + 1), vec![0; 4]); } } -*/ diff --git a/eth2/utils/ssz/src/lib.rs b/eth2/utils/ssz/src/lib.rs index 2c206dbf1..7fedd8e5f 100644 --- a/eth2/utils/ssz/src/lib.rs +++ b/eth2/utils/ssz/src/lib.rs @@ -1,18 +1,3 @@ -/* - * This is a WIP of implementing an alternative - * serialization strategy. It attempts to follow Vitalik's - * "simpleserialize" format here: - * https://github.com/ethereum/beacon_chain/blob/master/beacon_chain/utils/simpleserialize.py - * - * This implementation is not final and would almost certainly - * have issues. - */ -/* -extern crate bytes; -extern crate ethereum_types; - -pub mod decode; -*/ mod decode; mod encode; @@ -20,7 +5,7 @@ pub use decode::{Decodable, DecodeError, SszDecoderBuilder}; pub use encode::{Encodable, SszEncoder}; pub const BYTES_PER_LENGTH_OFFSET: usize = 4; -pub const MAX_LENGTH_VALUE: usize = 1 << (BYTES_PER_LENGTH_OFFSET * 8) - 1; +pub const MAX_LENGTH_VALUE: usize = (1 << (BYTES_PER_LENGTH_OFFSET * 8)) - 1; /// Convenience function to SSZ encode an object supporting ssz::Encode. /// @@ -31,208 +16,3 @@ where { val.as_ssz_bytes() } - -/* - -mod impl_decode; -mod impl_encode; - -pub use crate::decode::{decode, decode_ssz_list, Decodable, DecodeError}; -pub use crate::encode::{Encodable, SszStream}; - -pub use hashing::hash; - -pub const LENGTH_BYTES: usize = 4; -pub const MAX_LIST_SIZE: usize = 1 << (4 * 8); - - -#[cfg(test)] -mod tests { - extern crate hex; - extern crate yaml_rust; - - use self::yaml_rust::yaml; - use super::*; - use std::{fs::File, io::prelude::*, path::PathBuf}; - - #[test] - pub fn test_vector_uint_bounds() { - let mut file = { - let mut file_path_buf = PathBuf::from(env!("CARGO_MANIFEST_DIR")); - file_path_buf.push("src/test_vectors/uint_bounds.yaml"); - - File::open(file_path_buf).unwrap() - }; - let mut yaml_str = String::new(); - file.read_to_string(&mut yaml_str).unwrap(); - let docs = yaml::YamlLoader::load_from_str(&yaml_str).unwrap(); - let doc = &docs[0]; - - // Load test cases - let test_cases = doc["test_cases"].clone(); - - for test_case in test_cases { - // Only the valid cases are checked as parse::() will fail for all invalid cases - if test_case["valid"].as_bool().unwrap() { - // Convert test vector 'ssz' encoded yaml to Vec - let ssz = test_case["ssz"].as_str().unwrap().trim_start_matches("0x"); - let test_vector_bytes = hex::decode(ssz).unwrap(); - - // Convert test vector 'value' to ssz encoded bytes - let mut bytes: Vec; - match test_case["type"].as_str().unwrap() { - "uint8" => { - let value: u8 = test_case["value"].as_str().unwrap().parse::().unwrap(); - bytes = ssz_encode::(&value); // check encoding - - // Check decoding - let decoded = decode::(&test_vector_bytes).unwrap(); - assert_eq!(decoded, value); - } - "uint16" => { - let value: u16 = - test_case["value"].as_str().unwrap().parse::().unwrap(); - bytes = ssz_encode::(&value); - - // Check decoding - let decoded = decode::(&test_vector_bytes).unwrap(); - assert_eq!(decoded, value); - } - "uint32" => { - let value: u32 = - test_case["value"].as_str().unwrap().parse::().unwrap(); - bytes = ssz_encode::(&value); - - // Check decoding - let decoded = decode::(&test_vector_bytes).unwrap(); - assert_eq!(decoded, value); - } - "uint64" => { - let value: u64 = - test_case["value"].as_str().unwrap().parse::().unwrap(); - bytes = ssz_encode::(&value); - - // Check decoding - let decoded = decode::(&test_vector_bytes).unwrap(); - assert_eq!(decoded, value); - } - _ => continue, - }; - assert_eq!(test_vector_bytes, bytes); - } - } - } - - #[test] - pub fn test_vector_uint_random() { - let mut file = { - let mut file_path_buf = PathBuf::from(env!("CARGO_MANIFEST_DIR")); - file_path_buf.push("src/test_vectors/uint_random.yaml"); - - File::open(file_path_buf).unwrap() - }; - let mut yaml_str = String::new(); - file.read_to_string(&mut yaml_str).unwrap(); - let docs = yaml::YamlLoader::load_from_str(&yaml_str).unwrap(); - let doc = &docs[0]; - - // Load test cases - let test_cases = doc["test_cases"].clone(); - - for test_case in test_cases { - // Only the valid cases are checked as parse::() will fail for all invalid cases - if test_case["valid"].as_bool().unwrap() { - // Convert test vector 'ssz' encoded yaml to Vec - let ssz = test_case["ssz"].as_str().unwrap().trim_start_matches("0x"); - let test_vector_bytes = hex::decode(ssz).unwrap(); - - // Convert test vector 'value' to ssz encoded bytes - let mut bytes: Vec; - match test_case["type"].as_str().unwrap() { - "uint8" => { - let value: u8 = test_case["value"].as_str().unwrap().parse::().unwrap(); - bytes = ssz_encode::(&value); // check encoding - - // Check decoding - let decoded = decode::(&test_vector_bytes).unwrap(); - assert_eq!(decoded, value); - } - "uint16" => { - let value: u16 = - test_case["value"].as_str().unwrap().parse::().unwrap(); - bytes = ssz_encode::(&value); - - // Check decoding - let decoded = decode::(&test_vector_bytes).unwrap(); - assert_eq!(decoded, value); - } - "uint32" => { - let value: u32 = - test_case["value"].as_str().unwrap().parse::().unwrap(); - bytes = ssz_encode::(&value); - - // Check decoding - let decoded = decode::(&test_vector_bytes).unwrap(); - assert_eq!(decoded, value); - } - "uint64" => { - let value: u64 = - test_case["value"].as_str().unwrap().parse::().unwrap(); - bytes = ssz_encode::(&value); - - // Check decoding - let decoded = decode::(&test_vector_bytes).unwrap(); - assert_eq!(decoded, value); - } - _ => continue, - }; - assert_eq!(test_vector_bytes, bytes); - } - } - } - - #[test] - pub fn test_vector_uint_wrong_length() { - let mut file = { - let mut file_path_buf = PathBuf::from(env!("CARGO_MANIFEST_DIR")); - file_path_buf.push("src/test_vectors/uint_wrong_length.yaml"); - - File::open(file_path_buf).unwrap() - }; - let mut yaml_str = String::new(); - file.read_to_string(&mut yaml_str).unwrap(); - let docs = yaml::YamlLoader::load_from_str(&yaml_str).unwrap(); - let doc = &docs[0]; - - // Load test cases - let test_cases = doc["test_cases"].clone(); - - for test_case in test_cases { - // Convert test vector 'ssz' encoded yaml to Vec - let ssz = test_case["ssz"].as_str().unwrap().trim_start_matches("0x"); - let test_vector_bytes = hex::decode(ssz).unwrap(); - - // Attempt to decode invalid ssz bytes - match test_case["type"].as_str().unwrap() { - "uint8" => { - let decoded = decode::(&test_vector_bytes); - assert!(decoded.is_err()); - } - "uint16" => { - let decoded = decode::(&test_vector_bytes); - assert!(decoded.is_err()); - } - "uint32" => { - let decoded = decode::(&test_vector_bytes); - assert!(decoded.is_err()); - } - "uint64" => { - let decoded = decode::(&test_vector_bytes); - assert!(decoded.is_err()); - } - _ => continue, - }; - } - } -} -*/