From 91985e4b87bd13cf86a83e097f9221e01d4a161b Mon Sep 17 00:00:00 2001 From: hrxi Date: Mon, 10 Jun 2024 17:42:19 +0200 Subject: [PATCH] Add a way to mechanically generate `MAX_SIZE` constants --- Cargo.lock | 12 ++ Cargo.toml | 1 + bls/src/lazy.rs | 9 +- bls/src/types/aggregate_signature.rs | 5 +- bls/src/types/compressed_public_key.rs | 5 +- bls/src/types/compressed_signature.rs | 15 ++- bls/src/types/secret_key.rs | 16 ++- bls/src/types/signature.rs | 8 +- collections/src/bitset.rs | 3 +- keys/src/address.rs | 5 + keys/src/es256_public_key.rs | 5 + keys/src/es256_signature.rs | 5 + keys/src/public_key.rs | 5 + keys/src/signature.rs | 5 + macros/src/lib.rs | 4 + .../src/discovery/message_codec/header.rs | 8 +- .../src/discovery/message_codec/reader.rs | 4 +- .../src/discovery/message_codec/writer.rs | 4 +- primitives/block/src/equivocation_proof.rs | 45 +------ primitives/block/src/macro_block.rs | 41 +++--- primitives/block/src/micro_block.rs | 16 +-- primitives/block/src/tendermint.rs | 9 +- primitives/src/coin.rs | 9 +- primitives/src/networks.rs | 4 + primitives/src/slots_allocation.rs | 28 ++-- primitives/src/tendermint.rs | 12 +- primitives/transaction/src/reward.rs | 12 +- serde/Cargo.toml | 5 + serde/derive/Cargo.toml | 23 ++++ serde/derive/src/lib.rs | 127 ++++++++++++++++++ serde/src/lib.rs | 90 ++++++++++--- serde/tests/size.rs | 114 ++++++++++++++++ vrf/src/vrf.rs | 11 +- web-client/src/primitives/bls_secret_key.rs | 2 +- 34 files changed, 498 insertions(+), 169 deletions(-) create mode 100644 serde/derive/Cargo.toml create mode 100644 serde/derive/src/lib.rs create mode 100644 serde/tests/size.rs diff --git a/Cargo.lock b/Cargo.lock index 12cf5645c4..43ed1cfe1a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4462,11 +4462,23 @@ name = "nimiq-serde" version = "0.1.0" dependencies = [ "hex", + "nimiq-collections", + "nimiq-serde-derive", "postcard", "serde", "serde_derive", ] +[[package]] +name = "nimiq-serde-derive" +version = "0.1.0" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.66", +] + [[package]] name = "nimiq-spammer" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 24a599b559..41e7beedb3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,6 +41,7 @@ members = [ "rpc-interface", "rpc-server", "serde", + "serde/derive", "spammer", "tendermint", "test-log", diff --git a/bls/src/lazy.rs b/bls/src/lazy.rs index f84f4e9960..78cb8d4e94 100644 --- a/bls/src/lazy.rs +++ b/bls/src/lazy.rs @@ -139,16 +139,17 @@ impl From for CompressedPublicKey { } } -impl LazyPublicKey { - pub const SIZE: usize = CompressedPublicKey::SIZE; -} - #[cfg(feature = "serde-derive")] mod serialization { + use nimiq_serde::SerializedSize; use serde::{Deserialize, Serialize}; use super::*; + impl SerializedSize for LazyPublicKey { + const SIZE: usize = CompressedPublicKey::SIZE; + } + impl Serialize for LazyPublicKey { fn serialize(&self, serializer: S) -> Result where diff --git a/bls/src/types/aggregate_signature.rs b/bls/src/types/aggregate_signature.rs index 9aab97d490..b630d19eee 100644 --- a/bls/src/types/aggregate_signature.rs +++ b/bls/src/types/aggregate_signature.rs @@ -9,15 +9,12 @@ use crate::{CompressedSignature, Signature}; #[derive(Clone, Copy)] #[cfg_attr( feature = "serde-derive", - derive(serde::Serialize, serde::Deserialize), + derive(serde::Serialize, serde::Deserialize, nimiq_serde::SerializedSize), serde(transparent) )] pub struct AggregateSignature(pub Signature); impl AggregateSignature { - /// Maximum size in bytes for a single `AggregateSignature` in binary serialization. - pub const SIZE: usize = Signature::SIZE; - /// Creates a new "empty" aggregate signature. It is simply the identity element of the elliptic curve, also known as the point at infinity. pub fn new() -> Self { let signature = G1Projective::zero(); diff --git a/bls/src/types/compressed_public_key.rs b/bls/src/types/compressed_public_key.rs index 31dded52a2..d56c7fa734 100644 --- a/bls/src/types/compressed_public_key.rs +++ b/bls/src/types/compressed_public_key.rs @@ -19,14 +19,15 @@ use crate::PublicKey; feature = "serde-derive", derive( nimiq_hash_derive::SerializeContent, - nimiq_serde::Serialize, nimiq_serde::Deserialize, + nimiq_serde::Serialize, + nimiq_serde::SerializedMaxSize, ) )] #[cfg_attr(feature = "serde-derive", serde(transparent))] pub struct CompressedPublicKey { #[cfg_attr(feature = "serde-derive", serde(with = "nimiq_serde::HexArray"))] - pub public_key: [u8; 285], + pub public_key: [u8; CompressedPublicKey::SIZE], } impl CompressedPublicKey { diff --git a/bls/src/types/compressed_signature.rs b/bls/src/types/compressed_signature.rs index d460b351e6..c5309e4598 100644 --- a/bls/src/types/compressed_signature.rs +++ b/bls/src/types/compressed_signature.rs @@ -10,6 +10,8 @@ use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; use crate::Signature; +const SIZE: usize = 95; + /// The serialized compressed form of a signature. /// This form consists of the x-coordinate of the point (in the affine form), /// one bit indicating the sign of the y-coordinate @@ -19,18 +21,17 @@ use crate::Signature; feature = "serde-derive", derive( nimiq_hash_derive::SerializeContent, - nimiq_serde::Serialize, nimiq_serde::Deserialize, + nimiq_serde::Serialize, + nimiq_serde::SerializedSize, ) )] pub struct CompressedSignature { #[cfg_attr(feature = "serde-derive", serde(with = "nimiq_serde::HexArray"))] - pub signature: [u8; 95], + pub signature: [u8; SIZE], } impl CompressedSignature { - pub const SIZE: usize = 95; - /// Transforms the compressed form back into the projective form. pub fn uncompress(&self) -> Result { let affine_point: G1Affine = @@ -72,7 +73,7 @@ impl PartialOrd for CompressedSignature { impl Default for CompressedSignature { fn default() -> Self { CompressedSignature { - signature: [0u8; CompressedSignature::SIZE], + signature: [0u8; SIZE], } } } @@ -91,7 +92,7 @@ impl fmt::Display for CompressedSignature { impl From for CompressedSignature { fn from(signature: G1Projective) -> Self { - let mut buffer = [0u8; CompressedSignature::SIZE]; + let mut buffer = [0u8; SIZE]; CanonicalSerialize::serialize_compressed(&signature.into_affine(), &mut &mut buffer[..]) .unwrap(); CompressedSignature { signature: buffer } @@ -122,7 +123,7 @@ mod serde_derive { use std::str::FromStr; - use nimiq_serde::Deserialize; + use nimiq_serde::{Deserialize, SerializedSize}; use super::CompressedSignature; use crate::ParseError; diff --git a/bls/src/types/secret_key.rs b/bls/src/types/secret_key.rs index 9a9c4bf0e9..551e6cf4d6 100644 --- a/bls/src/types/secret_key.rs +++ b/bls/src/types/secret_key.rs @@ -5,6 +5,8 @@ use nimiq_utils::key_rng::{CryptoRng, RngCore, SecureGenerate}; use crate::{CompressedSignature, SigHash, Signature}; +const SIZE: usize = 95; + #[derive(Clone, Copy)] pub struct SecretKey { /// This is simply a number in the finite field Fr. @@ -13,8 +15,6 @@ pub struct SecretKey { } impl SecretKey { - pub const SIZE: usize = 95; - /// Creates a signature given a message. pub fn sign(&self, msg: &M) -> Signature { self.sign_hash(msg.hash()) @@ -64,13 +64,13 @@ mod serde_derive { use ark_mnt6_753::Fr; use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; - use nimiq_serde::Serialize as NimiqSerialize; + use nimiq_serde::{Serialize as _, SerializedSize}; use serde::{ de::{Deserialize, Deserializer, Error as SerializationError}, ser::{Error as DeSerializationError, Serialize, Serializer}, }; - use super::SecretKey; + use super::{SecretKey, SIZE}; impl fmt::Display for SecretKey { fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { @@ -84,12 +84,16 @@ mod serde_derive { } } + impl SerializedSize for SecretKey { + const SIZE: usize = SIZE; + } + impl Serialize for SecretKey { fn serialize(&self, serializer: S) -> Result where S: Serializer, { - let mut compressed = [0u8; Self::SIZE]; + let mut compressed = [0u8; SIZE]; self.secret_key .serialize_uncompressed(&mut compressed[..]) .map_err(|_| S::Error::custom("Couldn't compress secret key"))?; @@ -105,7 +109,7 @@ mod serde_derive { where D: Deserializer<'de>, { - let compressed: [u8; Self::SIZE] = + let compressed: [u8; SIZE] = nimiq_serde::FixedSizeByteArray::deserialize(deserializer)?.into_inner(); Ok(SecretKey { secret_key: Fr::deserialize_uncompressed(&*compressed.to_vec()) diff --git a/bls/src/types/signature.rs b/bls/src/types/signature.rs index 1a11d38c98..343d45b4e2 100644 --- a/bls/src/types/signature.rs +++ b/bls/src/types/signature.rs @@ -23,9 +23,6 @@ pub struct Signature { } impl Signature { - /// Maximum size in bytes for a single `Signature` in binary serialization. - pub const SIZE: usize = CompressedSignature::SIZE; - /// Maps an hash to a elliptic curve point in the G1 group, it is known as /// "hash-to-curve". It is required to create signatures. We use the /// try-and-increment method to create the EC point. @@ -139,6 +136,7 @@ impl From for Signature { #[cfg(feature = "serde-derive")] mod serde_derive { // TODO: Replace this with a generic serialization using `ToHex` and `FromHex`. + use nimiq_serde::SerializedSize; use serde::{ de::{Deserialize, Deserializer, Error}, ser::{Serialize, Serializer}, @@ -146,6 +144,10 @@ mod serde_derive { use super::{CompressedSignature, Signature}; + impl SerializedSize for Signature { + const SIZE: usize = CompressedSignature::SIZE; + } + impl Serialize for Signature { fn serialize(&self, serializer: S) -> Result where diff --git a/collections/src/bitset.rs b/collections/src/bitset.rs index b11cf09a44..9f8518ec35 100644 --- a/collections/src/bitset.rs +++ b/collections/src/bitset.rs @@ -4,6 +4,7 @@ use std::{ ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign}, }; +use nimiq_serde::SerializedMaxSize as _; use serde::{ de::{Error as _, SeqAccess, Visitor}, ser::SerializeSeq, @@ -374,7 +375,7 @@ impl BitSet { /// Maximum size in bytes for a single `BitSet` in binary serialization. pub const fn max_size(largest_bit_index: usize) -> usize { let num_u64 = (largest_bit_index + 63) / 64; - nimiq_serde::vec_max_size(nimiq_serde::U64_MAX_SIZE, num_u64) + nimiq_serde::seq_max_size(u64::MAX_SIZE, num_u64) } } diff --git a/keys/src/address.rs b/keys/src/address.rs index e29ed76c92..be364e9739 100644 --- a/keys/src/address.rs +++ b/keys/src/address.rs @@ -227,6 +227,7 @@ impl FromDatabaseValue for Address { mod serde_derive { use std::borrow::Cow; + use nimiq_serde::SerializedSize; use serde::{ de::{Deserialize, Deserializer, Error}, ser::{Serialize, Serializer}, @@ -234,6 +235,10 @@ mod serde_derive { use super::Address; + impl SerializedSize for Address { + const SIZE: usize = Address::SIZE; + } + impl Serialize for Address { fn serialize(&self, serializer: S) -> Result where diff --git a/keys/src/es256_public_key.rs b/keys/src/es256_public_key.rs index 6aa8619eef..582b3a84ab 100644 --- a/keys/src/es256_public_key.rs +++ b/keys/src/es256_public_key.rs @@ -93,6 +93,7 @@ impl std::hash::Hash for ES256PublicKey { mod serde_derive { use std::borrow::Cow; + use nimiq_serde::SerializedSize; use serde::{ de::{Deserialize, Deserializer, Error}, ser::{Serialize, Serializer}, @@ -100,6 +101,10 @@ mod serde_derive { use super::ES256PublicKey; + impl SerializedSize for ES256PublicKey { + const SIZE: usize = ES256PublicKey::SIZE; + } + impl Serialize for ES256PublicKey { fn serialize(&self, serializer: S) -> Result where diff --git a/keys/src/es256_signature.rs b/keys/src/es256_signature.rs index a24b1258d2..001d6b63b1 100644 --- a/keys/src/es256_signature.rs +++ b/keys/src/es256_signature.rs @@ -75,6 +75,7 @@ impl FromStr for ES256Signature { mod serde_derive { use std::borrow::Cow; + use nimiq_serde::SerializedSize; use serde::{ de::{Deserialize, Deserializer, Error}, ser::{Serialize, Serializer}, @@ -82,6 +83,10 @@ mod serde_derive { use super::ES256Signature; + impl SerializedSize for ES256Signature { + const SIZE: usize = ES256Signature::SIZE; + } + impl Serialize for ES256Signature { fn serialize(&self, serializer: S) -> Result where diff --git a/keys/src/public_key.rs b/keys/src/public_key.rs index 4209d8bbac..158dca9315 100644 --- a/keys/src/public_key.rs +++ b/keys/src/public_key.rs @@ -112,6 +112,7 @@ impl std::hash::Hash for Ed25519PublicKey { mod serde_derive { use std::borrow::Cow; + use nimiq_serde::SerializedSize; use serde::{ de::{Deserialize, Deserializer, Error}, ser::{Serialize, Serializer}, @@ -119,6 +120,10 @@ mod serde_derive { use super::Ed25519PublicKey; + impl SerializedSize for Ed25519PublicKey { + const SIZE: usize = Ed25519PublicKey::SIZE; + } + impl Serialize for Ed25519PublicKey { fn serialize(&self, serializer: S) -> Result where diff --git a/keys/src/signature.rs b/keys/src/signature.rs index 851f563389..b2a3c79d59 100644 --- a/keys/src/signature.rs +++ b/keys/src/signature.rs @@ -78,6 +78,7 @@ impl FromStr for Ed25519Signature { mod serde_derive { use std::borrow::Cow; + use nimiq_serde::SerializedSize; use serde::{ de::{Deserialize, Deserializer, Error}, ser::{Serialize, Serializer}, @@ -85,6 +86,10 @@ mod serde_derive { use super::Ed25519Signature; + impl SerializedSize for Ed25519Signature { + const SIZE: usize = Ed25519Signature::SIZE; + } + impl Serialize for Ed25519Signature { fn serialize(&self, serializer: S) -> Result where diff --git a/macros/src/lib.rs b/macros/src/lib.rs index c97784c513..87f0986346 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -76,6 +76,10 @@ macro_rules! create_typed_array { #[macro_export] macro_rules! add_serialization_fns_typed_arr { ($name: ident, $len: expr) => { + impl ::nimiq_macros::nimiq_serde::SerializedSize for $name { + const SIZE: usize = $len; + } + impl ::nimiq_macros::serde::Serialize for $name { fn serialize(&self, serializer: S) -> Result where diff --git a/network-libp2p/src/discovery/message_codec/header.rs b/network-libp2p/src/discovery/message_codec/header.rs index 8df3c454e2..fa2fcb4259 100644 --- a/network-libp2p/src/discovery/message_codec/header.rs +++ b/network-libp2p/src/discovery/message_codec/header.rs @@ -1,4 +1,4 @@ -use nimiq_serde::{fixint, Deserialize, Serialize}; +use nimiq_serde::{fixint, Deserialize, Serialize, SerializedSize}; #[derive(Clone, Debug, Serialize, Deserialize)] pub struct Header { @@ -10,11 +10,13 @@ pub struct Header { pub checksum: u32, } +impl SerializedSize for Header { + const SIZE: usize = 12; +} + impl Header { pub const MAGIC: u32 = 0x4204_2042; - pub const SIZE: usize = 12; - pub fn new(size: u32) -> Self { Self { magic: Self::MAGIC, diff --git a/network-libp2p/src/discovery/message_codec/reader.rs b/network-libp2p/src/discovery/message_codec/reader.rs index f767819f73..311dbfea75 100644 --- a/network-libp2p/src/discovery/message_codec/reader.rs +++ b/network-libp2p/src/discovery/message_codec/reader.rs @@ -6,7 +6,7 @@ use std::{ use bytes::BytesMut; use futures::{AsyncRead, Stream}; -use nimiq_serde::{Deserialize, DeserializeError}; +use nimiq_serde::{Deserialize, DeserializeError, SerializedSize as _}; use pin_project::pin_project; use super::header::Header; @@ -230,7 +230,7 @@ mod tests { use bytes::{BufMut, BytesMut}; use futures::{io::Cursor, StreamExt}; - use nimiq_serde::{Deserialize, Serialize}; + use nimiq_serde::{Deserialize, Serialize, SerializedSize}; use nimiq_test_log::test; use super::{Header, MessageReader}; diff --git a/network-libp2p/src/discovery/message_codec/writer.rs b/network-libp2p/src/discovery/message_codec/writer.rs index 219d5d816d..eaf7ca39c6 100644 --- a/network-libp2p/src/discovery/message_codec/writer.rs +++ b/network-libp2p/src/discovery/message_codec/writer.rs @@ -6,7 +6,7 @@ use std::{ use bytes::{Buf, BufMut, BytesMut}; use futures::{ready, AsyncWrite, Sink}; -use nimiq_serde::Serialize; +use nimiq_serde::{Serialize, SerializedSize as _}; use pin_project::pin_project; use super::header::Header; @@ -141,7 +141,7 @@ where #[cfg(test)] mod tests { use futures::SinkExt; - use nimiq_serde::{Deserialize, Serialize}; + use nimiq_serde::{Deserialize, Serialize, SerializedSize}; use nimiq_test_log::test; use super::{Header, MessageWriter}; diff --git a/primitives/block/src/equivocation_proof.rs b/primitives/block/src/equivocation_proof.rs index 21a13ff51b..9255dc5c87 100644 --- a/primitives/block/src/equivocation_proof.rs +++ b/primitives/block/src/equivocation_proof.rs @@ -12,7 +12,7 @@ use nimiq_primitives::{ slots_allocation::{Validator, Validators}, TendermintIdentifier, TendermintStep, TendermintVote, }; -use nimiq_serde::{Deserialize, Serialize}; +use nimiq_serde::{Deserialize, Serialize, SerializedMaxSize}; use nimiq_transaction::{ DoubleProposalLocator, DoubleVoteLocator, EquivocationLocator, ForkLocator, }; @@ -24,23 +24,13 @@ use crate::{MacroHeader, MicroHeader}; /// /// This can come in several forms, but e.g. producing two blocks in a single slot or voting twice /// in the same round. -#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, SerializedMaxSize)] pub enum EquivocationProof { Fork(ForkProof), DoubleProposal(DoubleProposalProof), DoubleVote(DoubleVoteProof), } -const fn max3(a: usize, b: usize, c: usize) -> usize { - if a > b && a > c { - a - } else if b > c { - b - } else { - c - } -} - fn get_validator<'a>( validators: &'a Validators, address: &Address, @@ -51,13 +41,6 @@ fn get_validator<'a>( } impl EquivocationProof { - /// The size of a single equivocation proof. This is the maximum possible size. - pub const MAX_SIZE: usize = 1 + max3( - ForkProof::MAX_SIZE, - DoubleProposalProof::MAX_SIZE, - DoubleVoteProof::MAX_SIZE, - ); - /// Locator of this proof. /// /// It is used to check that only one proof of an equivocation can be @@ -184,7 +167,7 @@ impl SerializeContent for EquivocationProof { /// Struct representing a fork proof. A fork proof proves that a given validator created or /// continued a fork. For this it is enough to provide two different headers, with the same block /// number, signed by the same validator. -#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, SerializedMaxSize)] pub struct ForkProof { /// Address of the offending validator. validator_address: Address, @@ -199,12 +182,6 @@ pub struct ForkProof { } impl ForkProof { - /// The size of a single fork proof. This is the maximum possible size, since the Micro header - /// has a variable size (because of the extra data field) and here we assume that the header - /// has the maximum size. - pub const MAX_SIZE: usize = - Address::SIZE + 2 * MicroHeader::MAX_SIZE + 2 * SchnorrSignature::SIZE; - pub fn new( validator_address: Address, mut header1: MicroHeader, @@ -332,7 +309,7 @@ pub enum EquivocationProofError { /// Struct representing a double proposal proof. A double proposal proof proves that a given /// validator created two macro block proposals at the same height, in the same round. -#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, SerializedMaxSize)] pub struct DoubleProposalProof { /// Address of the offending validator. validator_address: Address, @@ -347,10 +324,6 @@ pub struct DoubleProposalProof { } impl DoubleProposalProof { - /// The maximum size of a double proposal proof. - pub const MAX_SIZE: usize = - Address::SIZE + 2 * MacroHeader::MAX_SIZE + 2 * SchnorrSignature::SIZE; - pub fn new( validator_address: Address, mut header1: MacroHeader, @@ -461,7 +434,7 @@ impl SerializeContent for DoubleProposalProof { /// Struct representing a double vote proof. A double vote proof proves that a given /// validator voted twice at same height, in the same round. -#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, SerializedMaxSize)] pub struct DoubleVoteProof { /// Address of the offending validator. validator_address: Address, @@ -476,18 +449,14 @@ pub struct DoubleVoteProof { /// Aggregate signature for proposal 2. signature2: AggregateSignature, /// Signers for proposal 1. + #[serialize_size(bitset_max_elem = Policy::SLOTS as usize)] signers1: BitSet, /// Signers for proposal 2. + #[serialize_size(bitset_max_elem = Policy::SLOTS as usize)] signers2: BitSet, } impl DoubleVoteProof { - /// The maximum size of a double proposal proof. - pub const MAX_SIZE: usize = 2 * MacroHeader::MAX_SIZE - + 2 * nimiq_serde::option_max_size(Blake2sHash::SIZE) - + 2 * AggregateSignature::SIZE - + 2 * BitSet::max_size(Policy::SLOTS as usize); - pub fn new( tendermint_id: TendermintIdentifier, validator_address: Address, diff --git a/primitives/block/src/macro_block.rs b/primitives/block/src/macro_block.rs index c80a7e51cf..a3a49a619a 100644 --- a/primitives/block/src/macro_block.rs +++ b/primitives/block/src/macro_block.rs @@ -11,7 +11,7 @@ use nimiq_primitives::{ slots_allocation::{Validators, ValidatorsBuilder}, Message, PREFIX_TENDERMINT_PROPOSAL, }; -use nimiq_serde::{Deserialize, Serialize}; +use nimiq_serde::{Deserialize, Serialize, SerializedMaxSize, SerializedSize}; use nimiq_transaction::reward::RewardTransaction; use nimiq_vrf::VrfSeed; use thiserror::Error; @@ -19,7 +19,7 @@ use thiserror::Error; use crate::{tendermint::TendermintProof, BlockError}; /// The struct representing a Macro block (can be either checkpoint or election). -#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize, SerializedMaxSize)] pub struct MacroBlock { /// The header, contains some basic information and commitments to the body and the state. pub header: MacroHeader, @@ -31,12 +31,6 @@ pub struct MacroBlock { } impl MacroBlock { - #[allow(clippy::identity_op)] - pub const MAX_SIZE: usize = 0 - + /*header*/ MacroHeader::MAX_SIZE - + /*body*/ nimiq_serde::option_max_size(MacroBody::MAX_SIZE) - + /*justification*/ nimiq_serde::option_max_size(TendermintProof::MAX_SIZE); - /// Returns the network ID of this macro block. pub fn network(&self) -> NetworkId { self.header.network @@ -211,21 +205,19 @@ pub struct MacroHeader { pub history_root: Blake2bHash, } -impl MacroHeader { - /// Returns the size, in bytes, of a Macro block header. This represents the maximum possible - /// size since we assume that the extra_data field is completely filled. +impl SerializedMaxSize for MacroHeader { #[allow(clippy::identity_op)] - pub const MAX_SIZE: usize = 0 - + /*network*/ nimiq_serde::U8_SIZE - + /*version*/ nimiq_serde::U16_MAX_SIZE - + /*block_number*/ nimiq_serde::U32_MAX_SIZE - + /*round*/ nimiq_serde::U32_MAX_SIZE - + /*timestamp*/ nimiq_serde::U64_MAX_SIZE + const MAX_SIZE: usize = 0 + + /*network*/ NetworkId::SIZE + + /*version*/ u16::MAX_SIZE + + /*block_number*/ u32::MAX_SIZE + + /*round*/ u32::MAX_SIZE + + /*timestamp*/ u64::MAX_SIZE + /*parent_hash*/ Blake2bHash::SIZE + /*parent_election_hash*/ Blake2bHash::SIZE - + /*interlink*/ nimiq_serde::option_max_size(nimiq_serde::vec_max_size(Blake2bHash::SIZE, 32)) + + /*interlink*/ nimiq_serde::option_max_size(nimiq_serde::seq_max_size(Blake2bHash::SIZE, 32)) + /*seed*/ VrfSeed::SIZE - + /*extra_data*/ nimiq_serde::vec_max_size(nimiq_serde::U8_SIZE, 32) + + /*extra_data*/ nimiq_serde::seq_max_size(u8::SIZE, 32) + /*state_root*/ Blake2bHash::SIZE + /*body_root*/ Blake2sHash::SIZE + /*diff_root*/ Blake2bHash::SIZE @@ -282,7 +274,7 @@ impl SerializeContent for MacroHeader { } /// The struct representing the body of a Macro block (can be either checkpoint or election). -#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize, SerializedMaxSize)] pub struct MacroBody { /// Contains all the information regarding the next validator set, i.e. their validator /// public key, their reward address and their assigned validator slots. @@ -292,18 +284,14 @@ pub struct MacroBody { /// proposing macro blocks in the batch following this macro block. /// This set is needed for nodes that do not have the state as it is normally computed /// inside the staking contract. + #[serialize_size(bitset_max_elem = Policy::SLOTS as usize)] pub next_batch_initial_punished_set: BitSet, /// The reward related transactions of this block. + #[serialize_size(seq_max_elems = Policy::SLOTS as usize)] pub transactions: Vec, } impl MacroBody { - #[allow(clippy::identity_op)] - pub const MAX_SIZE: usize = 0 - + /*validators*/ nimiq_serde::option_max_size(Validators::MAX_SIZE) - + /*next_batch_initial_punished_set*/ BitSet::max_size(Policy::SLOTS as usize) - + /*transactions*/ nimiq_serde::vec_max_size(RewardTransaction::SIZE, Policy::SLOTS as usize); - pub(crate) fn verify(&self, is_election: bool) -> Result<(), BlockError> { if is_election != self.validators.is_some() { return Err(BlockError::InvalidValidators); @@ -350,6 +338,7 @@ mod test { #[test] fn size_well_below_msg_limit() { + use nimiq_serde::SerializedMaxSize; assert!( 2 * dbg!(MacroBlock::MAX_SIZE) + 16384 <= dbg!(nimiq_network_interface::network::MIN_SUPPORTED_MSG_SIZE) diff --git a/primitives/block/src/micro_block.rs b/primitives/block/src/micro_block.rs index c5a70d2e38..42d002b867 100644 --- a/primitives/block/src/micro_block.rs +++ b/primitives/block/src/micro_block.rs @@ -5,7 +5,7 @@ use nimiq_hash::{Blake2bHash, Blake2sHash, Hash}; use nimiq_hash_derive::SerializeContent; use nimiq_keys::{Ed25519PublicKey, Ed25519Signature}; use nimiq_primitives::{networks::NetworkId, policy::Policy, slots_allocation::Validators}; -use nimiq_serde::{Deserialize, Serialize}; +use nimiq_serde::{Deserialize, Serialize, SerializedMaxSize, SerializedSize}; use nimiq_transaction::{ExecutedTransaction, Transaction}; use nimiq_vrf::VrfSeed; @@ -196,18 +196,18 @@ pub struct MicroHeader { pub history_root: Blake2bHash, } -impl MicroHeader { +impl SerializedMaxSize for MicroHeader { /// Returns the size, in bytes, of a Micro block header. This represents the maximum possible /// size since we assume that the extra_data field is completely filled. #[allow(clippy::identity_op)] - pub const MAX_SIZE: usize = 0 - + /*network*/ nimiq_serde::U8_SIZE - + /*version*/ nimiq_serde::U16_MAX_SIZE - + /*block_number*/ nimiq_serde::U32_MAX_SIZE - + /*timestamp*/ nimiq_serde::U64_MAX_SIZE + const MAX_SIZE: usize = 0 + + /*network*/ NetworkId::SIZE + + /*version*/ u16::MAX_SIZE + + /*block_number*/ u32::MAX_SIZE + + /*timestamp*/ u64::MAX_SIZE + /*parent_hash*/ Blake2bHash::SIZE + /*seed*/ VrfSeed::SIZE - + /*extra_data*/ nimiq_serde::vec_max_size(nimiq_serde::U8_SIZE, 32) + + /*extra_data*/ nimiq_serde::seq_max_size(u8::SIZE, 32) + /*state_root*/ Blake2bHash::SIZE + /*body_root*/ Blake2sHash::SIZE + /*diff_root*/ Blake2bHash::SIZE diff --git a/primitives/block/src/tendermint.rs b/primitives/block/src/tendermint.rs index d053617dd6..01887a7750 100644 --- a/primitives/block/src/tendermint.rs +++ b/primitives/block/src/tendermint.rs @@ -4,12 +4,12 @@ use nimiq_primitives::{ policy::Policy, slots_allocation::Validators, TendermintIdentifier, TendermintStep, TendermintVote, }; -use nimiq_serde::{Deserialize, Serialize}; +use nimiq_serde::{Deserialize, Serialize, SerializedMaxSize}; use crate::{MacroBlock, MultiSignature}; /// The proof for a block produced by Tendermint. -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, SerializedMaxSize)] pub struct TendermintProof { // The round when the block was completed. This is necessary to verify the signature. pub round: u32, @@ -18,11 +18,6 @@ pub struct TendermintProof { } impl TendermintProof { - #[allow(clippy::identity_op)] - pub const MAX_SIZE: usize = 0 - + /*round*/ nimiq_serde::U32_MAX_SIZE - + /*sig*/ MultiSignature::MAX_SIZE; - /// This simply returns the number of slots that voted (precommitted) to this block. pub fn votes(&self) -> u16 { self.sig.signers.len() as u16 diff --git a/primitives/src/coin.rs b/primitives/src/coin.rs index 7938ab5348..e640559f99 100644 --- a/primitives/src/coin.rs +++ b/primitives/src/coin.rs @@ -257,12 +257,9 @@ impl FromStr for Coin { } } -impl Coin { - pub const SIZE: usize = 8; -} - #[cfg(feature = "serde-derive")] mod serialization { + use nimiq_serde::SerializedSize; use serde::{ de::{Error as DeError, Unexpected}, ser::Error as SerError, @@ -271,6 +268,10 @@ mod serialization { use super::*; + impl SerializedSize for Coin { + const SIZE: usize = 8; + } + impl Serialize for Coin { fn serialize(&self, serializer: S) -> Result where diff --git a/primitives/src/networks.rs b/primitives/src/networks.rs index 618b966300..a8cc8f7d4d 100644 --- a/primitives/src/networks.rs +++ b/primitives/src/networks.rs @@ -114,6 +114,10 @@ mod serde_derive { use super::NetworkId; + impl nimiq_serde::SerializedSize for NetworkId { + const SIZE: usize = 1; + } + impl serde::Serialize for NetworkId { fn serialize(&self, serializer: S) -> Result { if serializer.is_human_readable() { diff --git a/primitives/src/slots_allocation.rs b/primitives/src/slots_allocation.rs index de517b9ca8..b0e3ba1df4 100644 --- a/primitives/src/slots_allocation.rs +++ b/primitives/src/slots_allocation.rs @@ -41,7 +41,14 @@ pub struct Slot { /// A validator that owns some slots. #[derive(Debug, Clone, PartialEq, Eq)] -#[cfg_attr(feature = "serde-derive", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + feature = "serde-derive", + derive( + nimiq_serde::Deserialize, + nimiq_serde::Serialize, + nimiq_serde::SerializedMaxSize, + ) +)] pub struct Validator { pub address: Address, pub voting_key: LazyBlsPublicKey, @@ -52,13 +59,6 @@ pub struct Validator { } impl Validator { - #[allow(clippy::identity_op)] - pub const MAX_SIZE: usize = 0 - + /*address*/ Address::SIZE - + /*voting_key*/ LazyBlsPublicKey::SIZE - + /*signing_key*/ SchnorrPublicKey::SIZE - + /*slots*/ 2 * nimiq_serde::U16_MAX_SIZE; - /// Creates a new Validator. pub fn new, TSchnorrKey: Into>( address: Address, @@ -301,19 +301,21 @@ impl ValidatorsBuilder { } } -impl Validators { - pub const MAX_SIZE: usize = - nimiq_serde::vec_max_size(Validator::MAX_SIZE, Policy::SLOTS as usize); -} - #[cfg(feature = "serde-derive")] mod serde_derive { + use nimiq_serde::SerializedMaxSize; use serde::{ de::{Deserialize, Deserializer}, ser::{Serialize, Serializer}, }; use super::{Validator, Validators}; + use crate::policy::Policy; + + impl SerializedMaxSize for Validators { + const MAX_SIZE: usize = + nimiq_serde::seq_max_size(Validator::MAX_SIZE, Policy::SLOTS as usize); + } impl Serialize for Validators { fn serialize(&self, serializer: S) -> Result diff --git a/primitives/src/tendermint.rs b/primitives/src/tendermint.rs index f0064d63eb..8615ea1159 100644 --- a/primitives/src/tendermint.rs +++ b/primitives/src/tendermint.rs @@ -1,7 +1,7 @@ use std::io; use nimiq_hash::{Blake2sHash, SerializeContent}; -use nimiq_serde::{Deserialize, Serialize}; +use nimiq_serde::{Deserialize, Serialize, SerializedSize}; use serde_repr::{Deserialize_repr, Serialize_repr}; use crate::{ @@ -21,9 +21,8 @@ pub enum TendermintStep { Propose = PREFIX_TENDERMINT_PROPOSAL, } -impl TendermintStep { - /// Size in bytes for a `TendermintStep` in binary serialization. - pub const SIZE: usize = 1; +impl SerializedSize for TendermintStep { + const SIZE: usize = 1; } /// Unique identifier for a single instance of TendermintAggregation @@ -39,9 +38,8 @@ pub struct TendermintIdentifier { pub step: TendermintStep, } -impl TendermintIdentifier { - /// Maximum size in bytes for a `TendermintIdentifier` in binary serialization. - pub const MAX_SIZE: usize = nimiq_serde::U8_SIZE + 2 * 4 + TendermintStep::SIZE; +impl SerializedSize for TendermintIdentifier { + const SIZE: usize = u8::SIZE + 2 * 4 + TendermintStep::SIZE; } // Multiple things this needs to take care of when it comes to what needs signing here: diff --git a/primitives/transaction/src/reward.rs b/primitives/transaction/src/reward.rs index 9862c2d07e..cb23150780 100644 --- a/primitives/transaction/src/reward.rs +++ b/primitives/transaction/src/reward.rs @@ -1,8 +1,8 @@ use nimiq_keys::Address; use nimiq_primitives::coin::Coin; -use serde::{Deserialize, Serialize}; +use nimiq_serde::{Deserialize, Serialize, SerializedSize}; -#[derive(Clone, Eq, PartialEq, Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, SerializedSize)] pub struct RewardTransaction { /// The validator address of the rewarded validator. pub validator_address: Address, @@ -11,11 +11,3 @@ pub struct RewardTransaction { /// The reward amount. pub value: Coin, } - -impl RewardTransaction { - #[allow(clippy::identity_op)] - pub const SIZE: usize = 0 - + /*validator_address*/ Address::SIZE - + /*recipient*/ Address::SIZE - + /*value*/ Coin::SIZE; -} diff --git a/serde/Cargo.toml b/serde/Cargo.toml index ecd15c70bf..2ce7a9a2a8 100644 --- a/serde/Cargo.toml +++ b/serde/Cargo.toml @@ -18,3 +18,8 @@ hex = "0.4" postcard = { git = "https://github.com/nimiq/postcard-bytes", features = ["alloc"] } serde = "1.0" serde_derive = "1.0" + +nimiq-serde-derive = { path = "derive" } + +[dev-dependencies] +nimiq-collections = { workspace = true } diff --git a/serde/derive/Cargo.toml b/serde/derive/Cargo.toml new file mode 100644 index 0000000000..75c6b86fbb --- /dev/null +++ b/serde/derive/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "nimiq-serde-derive" +version.workspace = true +authors = ["The Nimiq Core Development Team "] +license.workspace = true +edition.workspace = true +description = "Derive macros for nimiq-serde" +homepage.workspace = true +repository.workspace = true +categories.workspace = true +keywords.workspace = true + +[lints] +workspace = true + +[lib] +proc-macro = true + +[dependencies] +darling = "0.20" +proc-macro2 = "1.0" +quote = "1.0" +syn = "2.0" diff --git a/serde/derive/src/lib.rs b/serde/derive/src/lib.rs new file mode 100644 index 0000000000..7ecb89349f --- /dev/null +++ b/serde/derive/src/lib.rs @@ -0,0 +1,127 @@ +use darling::{ + ast::{Data, Fields}, + FromDeriveInput, FromField, FromVariant, +}; +use proc_macro2::TokenStream; +use quote::quote; +use syn::{parse_macro_input, Expr, Generics, Ident, Type, TypeArray}; + +#[derive(FromDeriveInput)] +#[darling(attributes(serialize_size))] +struct Input { + ident: Ident, + generics: Generics, + data: Data, +} + +#[derive(FromField)] +#[darling(attributes(serialize_size))] +struct Field { + ty: Type, + seq_max_elems: Option, + bitset_max_elem: Option, +} + +#[derive(FromVariant)] +#[darling(attributes(serialize_size))] +struct Variant { + fields: Fields, +} + +fn size(ty: &Type) -> TokenStream { + if let Type::Array(TypeArray { elem, len, .. }) = ty { + let elem_size = size(elem); + return quote! { (#elem_size) * (#len) }; + } + quote! { <#ty as ::nimiq_serde::SerializedSize>::SIZE } +} + +fn max_size(ty: &Type) -> TokenStream { + quote! { <#ty as ::nimiq_serde::SerializedMaxSize>::MAX_SIZE } +} + +fn size_fields(fields: &[Field], max: bool) -> TokenStream { + let mut sum = quote! { 0 }; + for Field { + ty, + seq_max_elems, + bitset_max_elem, + } in fields + { + let size = match (seq_max_elems, bitset_max_elem) { + (None, None) => { + if max { + max_size(ty) + } else { + size(ty) + } + } + _ if !max => panic!( + "`#[serialize_size]` attributes not supported while deriving `SerializedSize`" + ), + (Some(seq_max_elems), None) => quote! { + ::nimiq_serde::seq_max_size(<#ty as ::nimiq_serde::SerializeSeqMaxSize>::Element::MAX_SIZE, #seq_max_elems) + }, + (None, Some(bitset_max_elem)) => quote! { + ::nimiq_collections::BitSet::max_size(#bitset_max_elem) + }, + _ => panic!("`more than one #[serialize_size]` attribute specified"), + }; + sum.extend(quote! { + #size }); + } + sum +} + +fn max_size_variants(variants: &[Variant]) -> TokenStream { + let mut max = quote! { 0 }; + for Variant { fields } in variants { + let size = size_fields(&fields.fields, true); + max = quote! { ::nimiq_serde::max(#max, #size) }; + } + let num_variants = u64::try_from(variants.len()).unwrap(); + quote! { ::nimiq_serde::uint_max_size(#num_variants) + #max } +} + +#[proc_macro_derive(SerializedSize, attributes(serialize_size))] +pub fn derive_serialize_size(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let Input { + ident, + generics, + data, + } = Input::from_derive_input(&parse_macro_input!(input)).unwrap(); + let (impl_generics, ty_generics, _) = generics.split_for_impl(); + + let size = match data { + Data::Enum(_) => panic!("`SerializedSize` cannot be derived for enums"), + Data::Struct(f) => size_fields(&f.fields, false), + }; + + quote! { + impl #impl_generics ::nimiq_serde::SerializedSize for #ident #ty_generics { + const SIZE: usize = #size; + } + } + .into() +} + +#[proc_macro_derive(SerializedMaxSize, attributes(serialize_size))] +pub fn derive_serialize_max_size(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let Input { + ident, + generics, + data, + } = Input::from_derive_input(&parse_macro_input!(input)).unwrap(); + let (impl_generics, ty_generics, _) = generics.split_for_impl(); + + let size = match data { + Data::Enum(v) => max_size_variants(&v), + Data::Struct(f) => size_fields(&f.fields, true), + }; + + quote! { + impl #impl_generics ::nimiq_serde::SerializedMaxSize for #ident #ty_generics { + const MAX_SIZE: usize = #size; + } + } + .into() +} diff --git a/serde/src/lib.rs b/serde/src/lib.rs index f92cc5abe0..51274dca42 100644 --- a/serde/src/lib.rs +++ b/serde/src/lib.rs @@ -1,5 +1,6 @@ -use std::{error::Error, fmt, io, io::Write}; +use std::{error::Error, fmt, io, io::Write, ops}; +pub use nimiq_serde_derive::{SerializedMaxSize, SerializedSize}; pub use postcard::{fixint, FixedSizeByteArray}; use serde::{ de::{Deserializer, Error as _}, @@ -44,23 +45,64 @@ impl fmt::Display for DeserializeError { impl Error for DeserializeError {} -/// Size in bytes for a single `i8` in binary serialization. -pub const I8_SIZE: usize = 1; -/// Size in bytes for a single `u8` in binary serialization. -pub const U8_SIZE: usize = 1; - -/// Maximum size in bytes for a single `i16` in binary serialization. -pub const I16_MAX_SIZE: usize = (16 + 6) / 7; -/// Maximum size in bytes for a single `u16` in binary serialization. -pub const U16_MAX_SIZE: usize = (16 + 6) / 7; -/// Maximum size in bytes for a single `i32` in binary serialization. -pub const I32_MAX_SIZE: usize = (32 + 6) / 7; -/// Maximum size in bytes for a single `u32` in binary serialization. -pub const U32_MAX_SIZE: usize = (32 + 6) / 7; -/// Maximum size in bytes for a single `i64` in binary serialization. -pub const I64_MAX_SIZE: usize = (64 + 6) / 7; -/// Maximum size in bytes for a single `u64` in binary serialization. -pub const U64_MAX_SIZE: usize = (64 + 6) / 7; +/// Types implementing this trait have a constant binary serialization size. +/// +/// It can be `#[derive]`d using the [`SerializedSize`] derive macro. +pub trait SerializedSize { + /// Size in bytes of the serialization. + const SIZE: usize; +} +/// Types implementing this trait have a maximum binary serialization size. +/// +/// It can be `#[derive]`d using the [`SerializedMaxSize`] derive macro. +pub trait SerializedMaxSize { + /// Maximum size in bytes of the serialization. + const MAX_SIZE: usize; +} + +impl SerializedMaxSize for T { + const MAX_SIZE: usize = T::SIZE; +} + +#[rustfmt::skip] +mod integer_impls { + use super::SerializedSize; + use super::SerializedMaxSize; + + impl SerializedSize for i8 { const SIZE: usize = 1; } + impl SerializedSize for u8 { const SIZE: usize = 1; } + + impl SerializedMaxSize for i16 { const MAX_SIZE: usize = (16 + 6) / 7; } + impl SerializedMaxSize for u16 { const MAX_SIZE: usize = (16 + 6) / 7; } + impl SerializedMaxSize for i32 { const MAX_SIZE: usize = (32 + 6) / 7; } + impl SerializedMaxSize for u32 { const MAX_SIZE: usize = (32 + 6) / 7; } + impl SerializedMaxSize for i64 { const MAX_SIZE: usize = (64 + 6) / 7; } + impl SerializedMaxSize for u64 { const MAX_SIZE: usize = (64 + 6) / 7; } +} + +impl SerializedMaxSize for Option { + const MAX_SIZE: usize = option_max_size(T::MAX_SIZE); +} + +impl SerializedMaxSize for Result { + const MAX_SIZE: usize = 1 + max(T::MAX_SIZE, E::MAX_SIZE); +} + +impl SerializedMaxSize for ops::Range { + const MAX_SIZE: usize = 2 * T::MAX_SIZE; +} + +impl SerializedMaxSize for [T; NUM] { + const MAX_SIZE: usize = NUM * T::MAX_SIZE; +} + +pub trait SerializeSeqMaxSize { + type Element; +} + +impl SerializeSeqMaxSize for Vec { + type Element = T; +} /// Maximum size in bytes for a integer value in binary serialization, given its maximum value. pub const fn uint_max_size(max_value: u64) -> usize { @@ -70,20 +112,30 @@ pub const fn uint_max_size(max_value: u64) -> usize { }; ((bits + 6) / 7) as usize } + /// Maximum size in bytes for an `Option` value in binary serialization. /// /// `inner_size` is the maximum size of its inner value `T`. pub const fn option_max_size(inner_size: usize) -> usize { 1 + inner_size } + /// Maximum size in bytes for a `Vec` value in binary serialization. /// /// `inner_size` is the maximum size of its inner value `T`. `max_elems` is the maximum number of /// elements in that `Vec`. -pub const fn vec_max_size(inner_size: usize, max_elems: usize) -> usize { +pub const fn seq_max_size(inner_size: usize, max_elems: usize) -> usize { uint_max_size(max_elems as u64) + inner_size * max_elems } +pub const fn max(a: usize, b: usize) -> usize { + if a >= b { + a + } else { + b + } +} + /// The Nimiq human readable array serialization helper trait /// /// ``` diff --git a/serde/tests/size.rs b/serde/tests/size.rs new file mode 100644 index 0000000000..402da658b7 --- /dev/null +++ b/serde/tests/size.rs @@ -0,0 +1,114 @@ +mod derive_exact { + use nimiq_serde::SerializedSize; + + struct Size123; + struct Size456; + + impl SerializedSize for Size123 { + const SIZE: usize = 123; + } + impl SerializedSize for Size456 { + const SIZE: usize = 456; + } + + #[test] + fn empty_struct() { + #[allow(dead_code)] + #[derive(SerializedSize)] + struct Empty; + + assert_eq!(Empty::SIZE, 0); + } + + #[test] + fn struct_() { + #[allow(dead_code)] + #[derive(SerializedSize)] + struct Struct(Size123, Size456); + + assert_eq!(Struct::SIZE, 123 + 456); + } + + #[test] + fn array() { + #[allow(dead_code)] + #[derive(SerializedSize)] + struct Struct([Size123; 456]); + + assert_eq!(Struct::SIZE, 123 * 456); + } +} + +mod derive_max { + use nimiq_collections::BitSet; + use nimiq_serde::{seq_max_size, SerializedMaxSize, SerializedSize as _}; + + struct Size123; + struct Size456; + + impl SerializedMaxSize for Size123 { + const MAX_SIZE: usize = 123; + } + impl SerializedMaxSize for Size456 { + const MAX_SIZE: usize = 456; + } + + #[test] + fn empty_struct() { + #[allow(dead_code)] + #[derive(SerializedMaxSize)] + struct Empty; + + assert_eq!(Empty::MAX_SIZE, 0); + } + + #[test] + fn vec() { + #[allow(dead_code)] + #[derive(SerializedMaxSize)] + struct Max32Bytes { + #[serialize_size(seq_max_elems = 32)] + bytes: Vec, + } + + assert_eq!(Max32Bytes::MAX_SIZE, seq_max_size(u8::SIZE, 32)); + } + + #[test] + fn option() { + assert_eq!(Option::::MAX_SIZE, 1 + 123); + } + + #[test] + fn bitset() { + #[allow(dead_code)] + #[derive(SerializedMaxSize)] + struct Wrapper { + #[serialize_size(bitset_max_elem = 123456)] + bitset: BitSet, + } + + assert_eq!(Wrapper::MAX_SIZE, BitSet::max_size(123456)); + } + + #[test] + fn struct_() { + #[allow(dead_code)] + #[derive(SerializedMaxSize)] + struct Struct(Size123, Size456); + + assert_eq!(Struct::MAX_SIZE, 123 + 456); + } + + #[test] + fn enum_() { + #[allow(dead_code)] + #[derive(SerializedMaxSize)] + enum Enum { + First(Size123), + Second(Size456), + } + + assert_eq!(Enum::MAX_SIZE, 1 + 456); + } +} diff --git a/vrf/src/vrf.rs b/vrf/src/vrf.rs index 3d3e806c51..9ac5717d62 100644 --- a/vrf/src/vrf.rs +++ b/vrf/src/vrf.rs @@ -60,7 +60,14 @@ impl fmt::Debug for VrfEntropy { } #[derive(Clone, Eq, PartialEq, Ord, PartialOrd)] -#[cfg_attr(feature = "serde-derive", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + feature = "serde-derive", + derive( + nimiq_serde::Deserialize, + nimiq_serde::Serialize, + nimiq_serde::SerializedSize, + ) +)] #[cfg_attr(feature = "serde-derive", serde(transparent))] /// A struct containing a VRF Seed. It is simply the serialized output of the VXEdDSA algorithm. /// @@ -79,7 +86,7 @@ pub struct VrfSeed { } impl VrfSeed { - pub const SIZE: usize = 96; + const SIZE: usize = 96; /// Verifies the current VRF Seed given the previous VRF Seed (which is part of the message) /// and the signer's public key. diff --git a/web-client/src/primitives/bls_secret_key.rs b/web-client/src/primitives/bls_secret_key.rs index 9807206039..01c41c0764 100644 --- a/web-client/src/primitives/bls_secret_key.rs +++ b/web-client/src/primitives/bls_secret_key.rs @@ -1,5 +1,5 @@ use nimiq_keys::SecureGenerate; -use nimiq_serde::{Deserialize, Serialize}; +use nimiq_serde::{Deserialize, Serialize, SerializedSize}; use wasm_bindgen::prelude::*; /// The secret part of the BLS keypair.