Merge current master and fix ssz-fuzzing

This commit is contained in:
Kirk Baird
2019-03-26 14:53:50 +11:00
190 changed files with 7696 additions and 3882 deletions

View File

@@ -10,4 +10,5 @@ hashing = { path = "../hashing" }
hex = "0.3"
serde = "1.0"
serde_derive = "1.0"
serde_hex = { path = "../serde_hex" }
ssz = { path = "../ssz" }

View File

@@ -2,7 +2,9 @@ use super::{AggregatePublicKey, Signature};
use bls_aggregates::{
AggregatePublicKey as RawAggregatePublicKey, AggregateSignature as RawAggregateSignature,
};
use serde::de::{Deserialize, Deserializer};
use serde::ser::{Serialize, Serializer};
use serde_hex::{encode as hex_encode, PrefixedHexVisitor};
use ssz::{
decode_ssz_list, hash, ssz_encode, Decodable, DecodeError, Encodable, SszStream, TreeHash,
};
@@ -82,7 +84,19 @@ impl Serialize for AggregateSignature {
where
S: Serializer,
{
serializer.serialize_bytes(&ssz_encode(self))
serializer.serialize_str(&hex_encode(ssz_encode(self)))
}
}
impl<'de> Deserialize<'de> for AggregateSignature {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let bytes = deserializer.deserialize_str(PrefixedHexVisitor)?;
let (obj, _) = <_>::ssz_decode(&bytes[..], 0)
.map_err(|e| serde::de::Error::custom(format!("invalid ssz ({:?})", e)))?;
Ok(obj)
}
}

View File

@@ -14,4 +14,8 @@ impl Keypair {
let pk = PublicKey::from_secret_key(&sk);
Keypair { sk, pk }
}
pub fn identifier(&self) -> String {
self.pk.concatenated_hex_id()
}
}

View File

@@ -1,9 +1,8 @@
use super::serde_vistors::HexVisitor;
use super::SecretKey;
use bls_aggregates::PublicKey as RawPublicKey;
use hex::encode as hex_encode;
use serde::de::{Deserialize, Deserializer};
use serde::ser::{Serialize, Serializer};
use serde_hex::{encode as hex_encode, PrefixedHexVisitor};
use ssz::{
decode, decode_ssz_list, hash, ssz_encode, Decodable, DecodeError, Encodable, SszStream,
TreeHash,
@@ -82,7 +81,7 @@ impl Serialize for PublicKey {
where
S: Serializer,
{
serializer.serialize_str(&hex_encode(ssz_encode(self)))
serializer.serialize_str(&hex_encode(self.as_raw().as_bytes()))
}
}
@@ -91,9 +90,9 @@ impl<'de> Deserialize<'de> for PublicKey {
where
D: Deserializer<'de>,
{
let bytes = deserializer.deserialize_str(HexVisitor)?;
let pubkey = decode::<PublicKey>(&bytes[..])
.map_err(|e| serde::de::Error::custom(format!("invalid ssz ({:?})", e)))?;
let bytes = deserializer.deserialize_str(PrefixedHexVisitor)?;
let pubkey = PublicKey::from_bytes(&bytes[..])
.map_err(|e| serde::de::Error::custom(format!("invalid pubkey ({:?})", e)))?;
Ok(pubkey)
}
}

View File

@@ -8,13 +8,14 @@ impl<'de> Visitor<'de> for HexVisitor {
type Value = Vec<u8>;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a hex string (without 0x prefix)")
formatter.write_str("a hex string (irrelevant of prefix)")
}
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
Ok(hex::decode(value).map_err(|e| de::Error::custom(format!("invalid hex ({:?})", e)))?)
Ok(hex::decode(value.trim_start_matches("0x"))
.map_err(|e| de::Error::custom(format!("invalid hex ({:?})", e)))?)
}
}

View File

@@ -14,27 +14,35 @@ use ssz::{
/// This struct is a wrapper upon a base type and provides helper functions (e.g., SSZ
/// serialization).
#[derive(Debug, PartialEq, Clone, Eq)]
pub struct Signature(RawSignature);
pub struct Signature {
signature: RawSignature,
is_empty: bool,
}
impl Signature {
/// Instantiate a new Signature from a message and a SecretKey.
pub fn new(msg: &[u8], domain: u64, sk: &SecretKey) -> Self {
Signature(RawSignature::new(msg, domain, sk.as_raw()))
Signature {
signature: RawSignature::new(msg, domain, sk.as_raw()),
is_empty: false,
}
}
/// Instantiate a new Signature from a message and a SecretKey, where the message has already
/// been hashed.
pub fn new_hashed(x_real_hashed: &[u8], x_imaginary_hashed: &[u8], sk: &SecretKey) -> Self {
Signature(RawSignature::new_hashed(
x_real_hashed,
x_imaginary_hashed,
sk.as_raw(),
))
Signature {
signature: RawSignature::new_hashed(x_real_hashed, x_imaginary_hashed, sk.as_raw()),
is_empty: false,
}
}
/// Verify the Signature against a PublicKey.
pub fn verify(&self, msg: &[u8], domain: u64, pk: &PublicKey) -> bool {
self.0.verify(msg, domain, pk.as_raw())
if self.is_empty {
return false;
}
self.signature.verify(msg, domain, pk.as_raw())
}
/// Verify the Signature against a PublicKey, where the message has already been hashed.
@@ -44,44 +52,72 @@ impl Signature {
x_imaginary_hashed: &[u8],
pk: &PublicKey,
) -> bool {
self.0
self.signature
.verify_hashed(x_real_hashed, x_imaginary_hashed, pk.as_raw())
}
/// Returns the underlying signature.
pub fn as_raw(&self) -> &RawSignature {
&self.0
&self.signature
}
/// Returns a new empty signature.
pub fn empty_signature() -> Self {
// Empty Signature is currently being represented as BLS::Signature.point_at_infinity()
// However it should be represented as vec![0; 96] but this
// would require all signatures to be represented in byte form as opposed to Signature
// Set RawSignature = infinity
let mut empty: Vec<u8> = vec![0; 96];
// Sets C_flag and B_flag to 1 and all else to 0
empty[0] += u8::pow(2, 6) + u8::pow(2, 7);
Signature(RawSignature::from_bytes(&empty).unwrap())
Signature {
signature: RawSignature::from_bytes(&empty).unwrap(),
is_empty: true,
}
}
// Converts a BLS Signature to bytes
pub fn as_bytes(&self) -> Vec<u8> {
if self.is_empty {
return vec![0; 96];
}
self.signature.as_bytes()
}
// Convert bytes to BLS Signature
pub fn from_bytes(bytes: &[u8]) -> Result<Self, DecodeError> {
for byte in bytes {
if *byte != 0 {
let raw_signature =
RawSignature::from_bytes(&bytes).map_err(|_| DecodeError::Invalid)?;
return Ok(Signature {
signature: raw_signature,
is_empty: false,
});
}
}
Ok(Signature::empty_signature())
}
// Check for empty Signature
pub fn is_empty(&self) -> bool {
self.is_empty
}
}
impl Encodable for Signature {
fn ssz_append(&self, s: &mut SszStream) {
s.append_vec(&self.0.as_bytes());
s.append_vec(&self.as_bytes());
}
}
impl Decodable for Signature {
fn ssz_decode(bytes: &[u8], i: usize) -> Result<(Self, usize), DecodeError> {
let (sig_bytes, i) = decode_ssz_list(bytes, i)?;
let raw_sig = RawSignature::from_bytes(&sig_bytes).map_err(|_| DecodeError::TooShort)?;
Ok((Signature(raw_sig), i))
let signature = Signature::from_bytes(&sig_bytes)?;
Ok((signature, i))
}
}
impl TreeHash for Signature {
fn hash_tree_root(&self) -> Vec<u8> {
hash(&self.0.as_bytes())
hash(&self.as_bytes())
}
}
@@ -100,7 +136,7 @@ impl<'de> Deserialize<'de> for Signature {
D: Deserializer<'de>,
{
let bytes = deserializer.deserialize_str(HexVisitor)?;
let signature = decode::<Signature>(&bytes[..])
let signature = Signature::from_bytes(&bytes[..])
.map_err(|e| serde::de::Error::custom(format!("invalid ssz ({:?})", e)))?;
Ok(signature)
}

View File

@@ -5,6 +5,7 @@ authors = ["Paul Hauner <paul@paulhauner.com>"]
edition = "2018"
[dependencies]
serde_hex = { path = "../serde_hex" }
ssz = { path = "../ssz" }
bit-vec = "0.5.0"
serde = "1.0"

View File

@@ -3,7 +3,9 @@ extern crate ssz;
use bit_vec::BitVec;
use serde::de::{Deserialize, Deserializer};
use serde::ser::{Serialize, Serializer};
use serde_hex::{encode, PrefixedHexVisitor};
use ssz::{Decodable, Encodable};
use std::cmp;
use std::default;
@@ -179,11 +181,25 @@ impl Decodable for BooleanBitfield {
}
impl Serialize for BooleanBitfield {
/// Serde serialization is compliant the Ethereum YAML test format.
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_bytes(&ssz::ssz_encode(self))
serializer.serialize_str(&encode(&ssz::ssz_encode(self)))
}
}
impl<'de> Deserialize<'de> for BooleanBitfield {
/// Serde serialization is compliant the Ethereum YAML test format.
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let bytes = deserializer.deserialize_str(PrefixedHexVisitor)?;
let (bitfield, _) = <_>::ssz_decode(&bytes[..], 0)
.map_err(|e| serde::de::Error::custom(format!("invalid ssz ({:?})", e)))?;
Ok(bitfield)
}
}

View File

@@ -0,0 +1,9 @@
[package]
name = "serde_hex"
version = "0.1.0"
authors = ["Paul Hauner <paul@paulhauner.com>"]
edition = "2018"
[dependencies]
serde = "1.0"
hex = "0.3"

View File

@@ -0,0 +1,59 @@
use hex;
use hex::ToHex;
use serde::de::{self, Visitor};
use std::fmt;
pub fn encode<T: AsRef<[u8]>>(data: T) -> String {
let mut hex = String::with_capacity(data.as_ref().len() * 2);
// Writing to a string never errors, so we can unwrap here.
data.write_hex(&mut hex).unwrap();
let mut s = "0x".to_string();
s.push_str(hex.as_str());
s
}
pub struct PrefixedHexVisitor;
impl<'de> Visitor<'de> for PrefixedHexVisitor {
type Value = Vec<u8>;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a hex string with 0x prefix")
}
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
if value.starts_with("0x") {
Ok(hex::decode(&value[2..])
.map_err(|e| de::Error::custom(format!("invalid hex ({:?})", e)))?)
} else {
Err(de::Error::custom("missing 0x prefix"))
}
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn encoding() {
let bytes = vec![0, 255];
let hex = encode(&bytes);
assert_eq!(hex.as_str(), "0x00ff");
let bytes = vec![];
let hex = encode(&bytes);
assert_eq!(hex.as_str(), "0x");
let bytes = vec![1, 2, 3];
let hex = encode(&bytes);
assert_eq!(hex.as_str(), "0x010203");
}
}

View File

@@ -9,7 +9,7 @@ publish = false
cargo-fuzz = true
[dependencies]
ethereum-types = "0.4.0"
ethereum-types = "0.5"
[dependencies.ssz]
path = ".."
@@ -84,22 +84,22 @@ path = "fuzz_targets/fuzz_target_address_decode.rs"
name = "fuzz_target_address_encode"
path = "fuzz_targets/fuzz_target_address_encode.rs"
[[bin]]
name = "fuzz_target_vec_decode"
path = "fuzz_targets/fuzz_target_vec_decode.rs"
[[bin]]
name = "fuzz_target_vec_address_decode"
path = "fuzz_targets/fuzz_target_vec_address_decode.rs"
[[bin]]
name = "fuzz_target_vec_u64_decode"
path = "fuzz_targets/fuzz_target_vec_u64_decode.rs"
[[bin]]
name = "fuzz_target_vec_bool_decode"
path = "fuzz_targets/fuzz_target_vec_bool_decode.rs"
[[bin]]
name = "fuzz_target_vec_decode"
path = "fuzz_targets/fuzz_target_vec_decode.rs"
[[bin]]
name = "fuzz_target_vec_encode"
path = "fuzz_targets/fuzz_target_vec_encode.rs"
[[bin]]
name = "fuzz_target_vec_u64_decode"
path = "fuzz_targets/fuzz_target_vec_u64_decode.rs"

View File

@@ -4,18 +4,17 @@ extern crate ethereum_types;
extern crate ssz;
use ethereum_types::Address;
use ssz::{DecodeError, Decodable};
use ssz::{DecodeError, decode};
// Fuzz ssz_decode()
fuzz_target!(|data: &[u8]| {
let result: Result<(Address, usize), DecodeError> = Decodable::ssz_decode(data, 0);
if data.len() >= 20 {
let result: Result<Address, DecodeError> = decode(data);
if data.len() == 20 {
// Should have valid result
let (address, index) = result.unwrap();
assert_eq!(index, 20);
let address = result.unwrap();
assert_eq!(address, Address::from_slice(&data[..20]));
} else {
// Length of less than 32 should return error
assert_eq!(result, Err(DecodeError::TooShort));
assert!(result.is_err());
}
});

View File

@@ -2,27 +2,23 @@
#[macro_use] extern crate libfuzzer_sys;
extern crate ssz;
use ssz::{DecodeError, Decodable};
use ssz::{DecodeError, decode};
// Fuzz ssz_decode()
fuzz_target!(|data: &[u8]| {
let result: Result<(bool, usize), DecodeError> = Decodable::ssz_decode(data, 0);
if data.len() >= 1 {
// TODO: change to little endian bytes
// https://github.com/sigp/lighthouse/issues/215
if data[0] == u8::pow(2,7) {
let (val_bool, index) = result.unwrap();
let result: Result<bool, DecodeError> = decode(data);
if data.len() == 1 {
if data[0] == 1 {
let val_bool = result.unwrap();
assert!(val_bool);
assert_eq!(index, 1);
} else if data[0] == 0 {
let (val_bool, index) = result.unwrap();
let val_bool = result.unwrap();
assert!(!val_bool);
assert_eq!(index, 1);
} else {
assert_eq!(result, Err(DecodeError::Invalid));
}
} else {
// Length of 0 should return error
assert_eq!(result, Err(DecodeError::TooShort));
assert!(result.is_err());
}
});

View File

@@ -15,8 +15,6 @@ fuzz_target!(|data: &[u8]| {
ssz.append(&val_bool);
let ssz = ssz.drain();
// TODO: change to little endian bytes
// https://github.com/sigp/lighthouse/issues/215
assert_eq!(val_bool, ssz[0] % u8::pow(2, 6));
assert_eq!(val_bool, ssz[0]);
assert_eq!(ssz.len(), 1);
});

View File

@@ -4,18 +4,17 @@ extern crate ethereum_types;
extern crate ssz;
use ethereum_types::H256;
use ssz::{DecodeError, Decodable};
use ssz::{DecodeError, decode};
// Fuzz ssz_decode()
fuzz_target!(|data: &[u8]| {
let result: Result<(H256, usize), DecodeError> = Decodable::ssz_decode(data, 0);
if data.len() >= 32 {
let result: Result<H256, DecodeError> = decode(data);
if data.len() == 32 {
// Should have valid result
let (hash, index) = result.unwrap();
assert_eq!(index, 32);
let hash = result.unwrap();
assert_eq!(hash, H256::from_slice(&data[..32]));
} else {
// Length of less than 32 should return error
assert_eq!(result, Err(DecodeError::TooShort));
assert!(result.is_err());
}
});

View File

@@ -2,21 +2,18 @@
#[macro_use] extern crate libfuzzer_sys;
extern crate ssz;
use ssz::{DecodeError, Decodable};
use ssz::{DecodeError, decode};
// Fuzz ssz_decode()
fuzz_target!(|data: &[u8]| {
let result: Result<(u16, usize), DecodeError> = Decodable::ssz_decode(data, 0);
if data.len() >= 2 {
let result: Result<u16, DecodeError> = decode(data);
if data.len() == 2 {
// Valid result
let (number_u16, index) = result.unwrap();
assert_eq!(index, 2);
// TODO: change to little endian bytes
// https://github.com/sigp/lighthouse/issues/215
let val = u16::from_be_bytes([data[0], data[1]]);
let number_u16 = result.unwrap();
let val = u16::from_le_bytes([data[0], data[1]]);
assert_eq!(number_u16, val);
} else {
// Length of 0 or 1 should return error
assert_eq!(result, Err(DecodeError::TooShort));
assert!(result.is_err());
}
});

View File

@@ -15,8 +15,6 @@ fuzz_target!(|data: &[u8]| {
ssz.append(&number_u16);
let ssz = ssz.drain();
// TODO: change to little endian bytes
// https://github.com/sigp/lighthouse/issues/215
assert_eq!(ssz.len(), 2);
assert_eq!(number_u16, u16::from_be_bytes([ssz[0], ssz[1]]));
assert_eq!(number_u16, u16::from_le_bytes([ssz[0], ssz[1]]));
});

View File

@@ -2,21 +2,18 @@
#[macro_use] extern crate libfuzzer_sys;
extern crate ssz;
use ssz::{DecodeError, Decodable};
use ssz::{DecodeError, decode};
// Fuzz ssz_decode()
fuzz_target!(|data: &[u8]| {
let result: Result<(u32, usize), DecodeError> = Decodable::ssz_decode(data, 0);
if data.len() >= 4 {
let result: Result<u32, DecodeError> = decode(data);
if data.len() == 4 {
// Valid result
let (number_u32, index) = result.unwrap();
assert_eq!(index, 4);
// TODO: change to little endian bytes
// https://github.com/sigp/lighthouse/issues/215
let val = u32::from_be_bytes([data[0], data[1], data[2], data[3]]);
let number_u32 = result.unwrap();
let val = u32::from_le_bytes([data[0], data[1], data[2], data[3]]);
assert_eq!(number_u32, val);
} else {
// Length less then 4 should return error
assert_eq!(result, Err(DecodeError::TooShort));
// Length not 4 should return error
assert!(result.is_err());
}
});

View File

@@ -15,8 +15,6 @@ fuzz_target!(|data: &[u8]| {
ssz.append(&number_u32);
let ssz = ssz.drain();
// TODO: change to little endian bytes
// https://github.com/sigp/lighthouse/issues/215
assert_eq!(ssz.len(), 4);
assert_eq!(number_u32, u32::from_be_bytes([ssz[0], ssz[1], ssz[2], ssz[3]]));
assert_eq!(number_u32, u32::from_le_bytes([ssz[0], ssz[1], ssz[2], ssz[3]]));
});

View File

@@ -2,18 +2,15 @@
#[macro_use] extern crate libfuzzer_sys;
extern crate ssz;
use ssz::{DecodeError, Decodable};
use ssz::{DecodeError, decode};
// Fuzz ssz_decode()
fuzz_target!(|data: &[u8]| {
let result: Result<(u64, usize), DecodeError> = Decodable::ssz_decode(data, 0);
if data.len() >= 8 {
let result: Result<u64, DecodeError> = decode(data);
if data.len() == 8 {
// Valid result
let (number_u64, index) = result.unwrap();
assert_eq!(index, 8);
// TODO: change to little endian bytes
// https://github.com/sigp/lighthouse/issues/215
let val = u64::from_be_bytes([
let number_u64 = result.unwrap();
let val = u64::from_le_bytes([
data[0],
data[1],
data[2],
@@ -25,7 +22,7 @@ fuzz_target!(|data: &[u8]| {
]);
assert_eq!(number_u64, val);
} else {
// Length less then 8 should return error
assert_eq!(result, Err(DecodeError::TooShort));
// Length not 8 should return error
assert!(result.is_err());
}
});

View File

@@ -9,7 +9,7 @@ fuzz_target!(|data: &[u8]| {
let mut ssz = SszStream::new();
let mut number_u64 = 0;
if data.len() >= 8 {
number_u64 = u64::from_be_bytes([
number_u64 = u64::from_le_bytes([
data[0],
data[1],
data[2],
@@ -24,10 +24,8 @@ fuzz_target!(|data: &[u8]| {
ssz.append(&number_u64);
let ssz = ssz.drain();
// TODO: change to little endian bytes
// https://github.com/sigp/lighthouse/issues/215
assert_eq!(ssz.len(), 8);
assert_eq!(number_u64, u64::from_be_bytes([
assert_eq!(number_u64, u64::from_le_bytes([
ssz[0],
ssz[1],
ssz[2],

View File

@@ -2,20 +2,17 @@
#[macro_use] extern crate libfuzzer_sys;
extern crate ssz;
use ssz::{DecodeError, Decodable};
use ssz::{DecodeError, decode};
// Fuzz ssz_decode()
fuzz_target!(|data: &[u8]| {
let result: Result<(u8, usize), DecodeError> = Decodable::ssz_decode(data, 0);
if data.len() >= 1 {
let result: Result<u8, DecodeError> = decode(data);
if data.len() == 1 {
// Should have valid result
let (number_u8, index) = result.unwrap();
// TODO: change to little endian bytes
// https://github.com/sigp/lighthouse/issues/215
assert_eq!(index, 1);
let number_u8 = result.unwrap();
assert_eq!(number_u8, data[0]);
} else {
// Length of 0 should return error
assert_eq!(result, Err(DecodeError::TooShort));
// Length not 1 should return error
assert!(result.is_err());
}
});

View File

@@ -15,8 +15,6 @@ fuzz_target!(|data: &[u8]| {
ssz.append(&number_u8);
let ssz = ssz.drain();
// TODO: change to little endian bytes
// https://github.com/sigp/lighthouse/issues/215
assert_eq!(number_u8, ssz[0]);
assert_eq!(ssz.len(), 1);
});

View File

@@ -2,19 +2,16 @@
#[macro_use] extern crate libfuzzer_sys;
extern crate ssz;
use ssz::{DecodeError, Decodable};
use ssz::{DecodeError, decode};
// Fuzz ssz_decode()
// Fuzz decode()
fuzz_target!(|data: &[u8]| {
// Note: we assume architecture is 64 bit -> usize == 64 bits
let result: Result<(usize, usize), DecodeError> = Decodable::ssz_decode(data, 0);
if data.len() >= 8 {
let result: Result<usize, DecodeError> = decode(data);
if data.len() == 8 {
// Valid result
let (number_usize, index) = result.unwrap();
assert_eq!(index, 8);
// TODO: change to little endian bytes
// https://github.com/sigp/lighthouse/issues/215
let val = u64::from_be_bytes([
let number_usize = result.unwrap();
let val = u64::from_le_bytes([
data[0],
data[1],
data[2],
@@ -27,6 +24,6 @@ fuzz_target!(|data: &[u8]| {
assert_eq!(number_usize, val as usize);
} else {
// Length less then 8 should return error
assert_eq!(result, Err(DecodeError::TooShort));
assert!(result.is_err());
}
});

View File

@@ -9,7 +9,7 @@ fuzz_target!(|data: &[u8]| {
let mut ssz = SszStream::new();
let mut number_usize = 0;
if data.len() >= 8 {
number_usize = u64::from_be_bytes([
number_usize = u64::from_le_bytes([
data[0],
data[1],
data[2],
@@ -24,10 +24,8 @@ fuzz_target!(|data: &[u8]| {
ssz.append(&number_usize);
let ssz = ssz.drain();
// TODO: change to little endian bytes
// https://github.com/sigp/lighthouse/issues/215
assert_eq!(ssz.len(), 8);
assert_eq!(number_usize, u64::from_be_bytes([
assert_eq!(number_usize, u64::from_le_bytes([
ssz[0],
ssz[1],
ssz[2],

View File

@@ -4,9 +4,9 @@ extern crate ethereum_types;
extern crate ssz;
use ethereum_types::{Address};
use ssz::{DecodeError, Decodable};
use ssz::{decode, DecodeError};
// Fuzz ssz_decode()
fuzz_target!(|data: &[u8]| {
let _result: Result<(Vec<Address>, usize), DecodeError> = Decodable::ssz_decode(data, 0);
let _result: Result<Vec<Address>, DecodeError> = decode(data);
});

View File

@@ -2,9 +2,9 @@
#[macro_use] extern crate libfuzzer_sys;
extern crate ssz;
use ssz::{DecodeError, Decodable};
use ssz::{decode, DecodeError};
// Fuzz ssz_decode()
fuzz_target!(|data: &[u8]| {
let _result: Result<(Vec<bool>, usize), DecodeError> = Decodable::ssz_decode(data, 0);
let _result: Result<Vec<bool>, DecodeError> = decode(data);
});

View File

@@ -3,10 +3,9 @@
extern crate ethereum_types;
extern crate ssz;
use ethereum_types::{Address, H256};
use ssz::{DecodeError, Decodable};
use ssz::{decode, DecodeError, Decodable};
// Fuzz ssz_decode()
fuzz_target!(|data: &[u8]| {
let _result: Result<(Vec<u8>, usize), DecodeError> = Decodable::ssz_decode(data, 0);
let _result: Result<Vec<u8>, DecodeError> = decode(data);
});

View File

@@ -3,7 +3,6 @@
extern crate ethereum_types;
extern crate ssz;
use ethereum_types::{Address, H256};
use ssz::SszStream;
// Fuzz ssz_encode()

View File

@@ -2,9 +2,9 @@
#[macro_use] extern crate libfuzzer_sys;
extern crate ssz;
use ssz::{DecodeError, Decodable};
use ssz::{decode, DecodeError};
// Fuzz ssz_decode()
fuzz_target!(|data: &[u8]| {
let _result: Result<(Vec<u64>, usize), DecodeError> = Decodable::ssz_decode(data, 0);
let _result: Result<Vec<u64>, DecodeError> = decode(data);
});

View File

@@ -24,11 +24,30 @@ macro_rules! impl_decodable_for_uint {
};
}
macro_rules! impl_decodable_for_u8_array {
($len: expr) => {
impl Decodable for [u8; $len] {
fn ssz_decode(bytes: &[u8], index: usize) -> Result<(Self, usize), DecodeError> {
if index + $len > bytes.len() {
Err(DecodeError::TooShort)
} else {
let mut array: [u8; $len] = [0; $len];
array.copy_from_slice(&bytes[index..index + $len]);
Ok((array, index + $len))
}
}
}
};
}
impl_decodable_for_uint!(u16, 16);
impl_decodable_for_uint!(u32, 32);
impl_decodable_for_uint!(u64, 64);
impl_decodable_for_uint!(usize, 64);
impl_decodable_for_u8_array!(4);
impl Decodable for u8 {
fn ssz_decode(bytes: &[u8], index: usize) -> Result<(Self, usize), DecodeError> {
if index >= bytes.len() {
@@ -276,4 +295,12 @@ mod tests {
let decoded_array: Result<Vec<u16>, DecodeError> = decode(&encoded);
assert_eq!(decoded_array, Err(DecodeError::TooLong));
}
#[test]
fn test_decode_u8_array() {
let ssz = vec![0, 1, 2, 3];
let result: [u8; 4] = decode(&ssz).unwrap();
assert_eq!(result.len(), 4);
assert_eq!(result, [0, 1, 2, 3]);
}
}

View File

@@ -40,12 +40,25 @@ macro_rules! impl_encodable_for_uint {
};
}
macro_rules! impl_encodable_for_u8_array {
($len: expr) => {
impl Encodable for [u8; $len] {
fn ssz_append(&self, s: &mut SszStream) {
let bytes: Vec<u8> = self.iter().cloned().collect();
s.append_encoded_raw(&bytes);
}
}
};
}
impl_encodable_for_uint!(u8, 8);
impl_encodable_for_uint!(u16, 16);
impl_encodable_for_uint!(u32, 32);
impl_encodable_for_uint!(u64, 64);
impl_encodable_for_uint!(usize, 64);
impl_encodable_for_u8_array!(4);
impl Encodable for bool {
fn ssz_append(&self, s: &mut SszStream) {
let byte = if *self { 0b0000_0001 } else { 0b0000_0000 };
@@ -77,6 +90,7 @@ where
#[cfg(test)]
mod tests {
use super::*;
use crate::ssz_encode;
#[test]
fn test_ssz_encode_h256() {
@@ -247,4 +261,15 @@ mod tests {
ssz.append(&x);
assert_eq!(ssz.drain(), vec![0b0000_0001]);
}
#[test]
fn test_ssz_encode_u8_array() {
let x: [u8; 4] = [0, 1, 7, 8];
let ssz = ssz_encode(&x);
assert_eq!(ssz, vec![0, 1, 7, 8]);
let x: [u8; 4] = [255, 255, 255, 255];
let ssz = ssz_encode(&x);
assert_eq!(ssz, vec![255, 255, 255, 255]);
}
}

View File

@@ -56,10 +56,46 @@ fn get_named_field_idents<'a>(struct_data: &'a syn::DataStruct) -> Vec<&'a syn::
.collect()
}
/// Returns a Vec of `syn::Ident` for each named field in the struct, whilst filtering out fields
/// that should not be serialized.
///
/// # Panics
/// Any unnamed struct field (like in a tuple struct) will raise a panic at compile time.
fn get_serializable_named_field_idents<'a>(
struct_data: &'a syn::DataStruct,
) -> Vec<&'a syn::Ident> {
struct_data
.fields
.iter()
.filter_map(|f| {
if should_skip_serializing(&f) {
None
} else {
Some(match &f.ident {
Some(ref ident) => ident,
_ => panic!("ssz_derive only supports named struct fields."),
})
}
})
.collect()
}
/// Returns true if some field has an attribute declaring it should not be serialized.
///
/// The field attribute is: `#[ssz(skip_serializing)]`
fn should_skip_serializing(field: &syn::Field) -> bool {
for attr in &field.attrs {
if attr.tts.to_string() == "( skip_serializing )" {
return true;
}
}
false
}
/// Implements `ssz::Encodable` for some `struct`.
///
/// Fields are encoded in the order they are defined.
#[proc_macro_derive(Encode)]
#[proc_macro_derive(Encode, attributes(ssz))]
pub fn ssz_encode_derive(input: TokenStream) -> TokenStream {
let item = parse_macro_input!(input as DeriveInput);
@@ -70,7 +106,7 @@ pub fn ssz_encode_derive(input: TokenStream) -> TokenStream {
_ => panic!("ssz_derive only supports structs."),
};
let field_idents = get_named_field_idents(&struct_data);
let field_idents = get_serializable_named_field_idents(&struct_data);
let output = quote! {
impl ssz::Encodable for #name {
@@ -84,6 +120,18 @@ pub fn ssz_encode_derive(input: TokenStream) -> TokenStream {
output.into()
}
/// Returns true if some field has an attribute declaring it should not be deserialized.
///
/// The field attribute is: `#[ssz(skip_deserializing)]`
fn should_skip_deserializing(field: &syn::Field) -> bool {
for attr in &field.attrs {
if attr.tts.to_string() == "( skip_deserializing )" {
return true;
}
}
false
}
/// Implements `ssz::Decodable` for some `struct`.
///
/// Fields are decoded in the order they are defined.
@@ -98,26 +146,39 @@ pub fn ssz_decode_derive(input: TokenStream) -> TokenStream {
_ => panic!("ssz_derive only supports structs."),
};
let field_idents = get_named_field_idents(&struct_data);
let all_idents = get_named_field_idents(&struct_data);
// Using a var in an iteration always consumes the var, therefore we must make a `fields_a` and
// a `fields_b` in order to perform two loops.
//
// https://github.com/dtolnay/quote/issues/8
let field_idents_a = &field_idents;
let field_idents_b = &field_idents;
// Build quotes for fields that should be deserialized and those that should be built from
// `Default`.
let mut quotes = vec![];
for field in &struct_data.fields {
match &field.ident {
Some(ref ident) => {
if should_skip_deserializing(field) {
quotes.push(quote! {
let #ident = <_>::default();
});
} else {
quotes.push(quote! {
let (#ident, i) = <_>::ssz_decode(bytes, i)?;
});
}
}
_ => panic!("ssz_derive only supports named struct fields."),
};
}
let output = quote! {
impl ssz::Decodable for #name {
fn ssz_decode(bytes: &[u8], i: usize) -> Result<(Self, usize), ssz::DecodeError> {
#(
let (#field_idents_a, i) = <_>::ssz_decode(bytes, i)?;
#quotes
)*
Ok((
Self {
#(
#field_idents_b,
#all_idents,
)*
},
i
@@ -128,10 +189,46 @@ pub fn ssz_decode_derive(input: TokenStream) -> TokenStream {
output.into()
}
/// Returns a Vec of `syn::Ident` for each named field in the struct, whilst filtering out fields
/// that should not be tree hashed.
///
/// # Panics
/// Any unnamed struct field (like in a tuple struct) will raise a panic at compile time.
fn get_tree_hashable_named_field_idents<'a>(
struct_data: &'a syn::DataStruct,
) -> Vec<&'a syn::Ident> {
struct_data
.fields
.iter()
.filter_map(|f| {
if should_skip_tree_hash(&f) {
None
} else {
Some(match &f.ident {
Some(ref ident) => ident,
_ => panic!("ssz_derive only supports named struct fields."),
})
}
})
.collect()
}
/// Returns true if some field has an attribute declaring it should not be tree-hashed.
///
/// The field attribute is: `#[tree_hash(skip_hashing)]`
fn should_skip_tree_hash(field: &syn::Field) -> bool {
for attr in &field.attrs {
if attr.tts.to_string() == "( skip_hashing )" {
return true;
}
}
false
}
/// Implements `ssz::TreeHash` for some `struct`.
///
/// Fields are processed in the order they are defined.
#[proc_macro_derive(TreeHash)]
#[proc_macro_derive(TreeHash, attributes(tree_hash))]
pub fn ssz_tree_hash_derive(input: TokenStream) -> TokenStream {
let item = parse_macro_input!(input as DeriveInput);
@@ -142,7 +239,7 @@ pub fn ssz_tree_hash_derive(input: TokenStream) -> TokenStream {
_ => panic!("ssz_derive only supports structs."),
};
let field_idents = get_named_field_idents(&struct_data);
let field_idents = get_tree_hashable_named_field_idents(&struct_data);
let output = quote! {
impl ssz::TreeHash for #name {

View File

@@ -4,7 +4,20 @@ use crate::proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, DeriveInput};
#[proc_macro_derive(TestRandom)]
/// Returns true if some field has an attribute declaring it should be generated from default (not
/// randomized).
///
/// The field attribute is: `#[test_random(default)]`
fn should_use_default(field: &syn::Field) -> bool {
for attr in &field.attrs {
if attr.tts.to_string() == "( default )" {
return true;
}
}
false
}
#[proc_macro_derive(TestRandom, attributes(test_random))]
pub fn test_random_derive(input: TokenStream) -> TokenStream {
let derived_input = parse_macro_input!(input as DeriveInput);
let name = &derived_input.ident;
@@ -14,14 +27,32 @@ pub fn test_random_derive(input: TokenStream) -> TokenStream {
_ => panic!("test_random_derive only supports structs."),
};
let field_names = get_named_field_idents(&struct_data);
// Build quotes for fields that should be generated and those that should be built from
// `Default`.
let mut quotes = vec![];
for field in &struct_data.fields {
match &field.ident {
Some(ref ident) => {
if should_use_default(field) {
quotes.push(quote! {
#ident: <_>::default(),
});
} else {
quotes.push(quote! {
#ident: <_>::random_for_test(rng),
});
}
}
_ => panic!("test_random_derive only supports named struct fields."),
};
}
let output = quote! {
impl<T: RngCore> TestRandom<T> for #name {
fn random_for_test(rng: &mut T) -> Self {
Self {
#(
#field_names: <_>::random_for_test(rng),
#quotes
)*
}
}
@@ -30,14 +61,3 @@ pub fn test_random_derive(input: TokenStream) -> TokenStream {
output.into()
}
fn get_named_field_idents(struct_data: &syn::DataStruct) -> Vec<(&syn::Ident)> {
struct_data
.fields
.iter()
.map(|f| match &f.ident {
Some(ref ident) => ident,
_ => panic!("test_random_derive only supports named struct fields."),
})
.collect()
}