diff --git a/.github/workflows/build_stable.yml b/.github/workflows/build_stable.yml index 67fd659d..f038d5d4 100644 --- a/.github/workflows/build_stable.yml +++ b/.github/workflows/build_stable.yml @@ -78,6 +78,11 @@ jobs: env: RUSTUP_TOOLCHAIN: stable + - name: Build package without default features + run: cargo build -p ${{ inputs.package }} --no-default-features + env: + RUSTUP_TOOLCHAIN: stable + - name: Test package run: cargo test -p ${{ inputs.package }} --features ${{ inputs.features }} env: diff --git a/maint/codeql/rust/zeroize.ql b/maint/codeql/rust/zeroize.ql index aba222b6..0c9b8ac7 100644 --- a/maint/codeql/rust/zeroize.ql +++ b/maint/codeql/rust/zeroize.ql @@ -210,6 +210,11 @@ predicate callsCtEq(Function f) { mc.getEnclosingCallable() = f and mc.getIdentifier().getText() = "ct_eq" ) + or + exists(PathExpr pe | + pe.getEnclosingCallable() = f and + pe.getPath().getSegment().getIdentifier().getText() = "ct_eq" + ) } /** diff --git a/pkgs/dev/Cargo.toml b/pkgs/dev/Cargo.toml index 7ad57123..3bd71d73 100644 --- a/pkgs/dev/Cargo.toml +++ b/pkgs/dev/Cargo.toml @@ -48,7 +48,9 @@ xxhash-rust = { version = "0.8", features = ["xxh32"] } [dependencies] bitcoin-consensus-encoding = { workspace = true, features = ["alloc"] } cfg-if = "1" -dash-num = { version = "0.0.0", path = "../num", optional = true } +dash-num = { version = "0.0.0", path = "../num", optional = true, features = [ + "codec", +] } dash-params = { version = "0.0.0", path = "../params", optional = true } dash-pow = { version = "0.0.0", path = "../pow", optional = true } dash-primitives = { version = "0.0.0", path = "../primitives" } diff --git a/pkgs/num/Cargo.toml b/pkgs/num/Cargo.toml index 7e2eade4..11579330 100644 --- a/pkgs/num/Cargo.toml +++ b/pkgs/num/Cargo.toml @@ -6,15 +6,14 @@ license = "MIT" [features] default = [] -std = ["bitcoin-consensus-encoding/std", "dash-types/std"] -full = ["std", "serde"] +std = ["bitcoin-consensus-encoding?/std", "dash-types/std"] +full = ["std", "codec", "serde"] +codec = ["dep:bitcoin-consensus-encoding", "dash-types/codec"] serde = ["dep:serde", "dash-types/serde"] [dependencies] -bitcoin-consensus-encoding = { workspace = true } -dash-types = { version = "0.0.0", path = "../types", default-features = false, features = [ - "codec", -] } +bitcoin-consensus-encoding = { workspace = true, optional = true } +dash-types = { version = "0.0.0", path = "../types", default-features = false } serde = { version = "1", default-features = false, features = [ "derive", "alloc", diff --git a/pkgs/num/src/compact.rs b/pkgs/num/src/compact.rs index 6b5cef67..f9f404fa 100644 --- a/pkgs/num/src/compact.rs +++ b/pkgs/num/src/compact.rs @@ -8,7 +8,9 @@ use crate::Arith256; +#[cfg(feature = "codec")] use dash_types::codec::NumCodec; +#[cfg(feature = "codec")] use dash_types::impl_num; use core::fmt; @@ -31,6 +33,7 @@ pub struct DecodedTarget { pub overflow: bool, } +#[cfg(feature = "codec")] impl NumCodec for CompactTarget { fn from_base(v: u32) -> Self { Self(v) @@ -41,6 +44,7 @@ impl NumCodec for CompactTarget { } } +#[cfg(feature = "codec")] impl_num!(CompactTarget, u32); impl CompactTarget { diff --git a/pkgs/num/src/hash.rs b/pkgs/num/src/hash.rs index 252ca4e3..8700fee3 100644 --- a/pkgs/num/src/hash.rs +++ b/pkgs/num/src/hash.rs @@ -301,17 +301,19 @@ macro_rules! define_hash { } } - impl $crate::__private::dash_types::codec::BaseCodec for $name { - fn decode(data: &mut &[u8]) -> Result { - $crate::__private::dash_types::codec::take::<$n>(data).map(Self::from_bytes) - } + $crate::cfg_codec! { + impl $crate::__private::dash_types::codec::BaseCodec for $name { + fn decode(data: &mut &[u8]) -> Result { + $crate::__private::dash_types::codec::take::<$n>(data).map(Self::from_bytes) + } - fn encode(&self, buf: &mut impl crate::__private::dash_types::codec::EncodeBuf) { - buf.extend_from_slice(&self.0); + fn encode(&self, buf: &mut impl $crate::__private::dash_types::codec::EncodeBuf) { + buf.extend_from_slice(&self.0); + } } - } - $crate::__private::dash_types::impl_type!($name); + $crate::__private::dash_types::impl_type!($name); + } #[cfg(feature = "serde")] impl ::serde::Serialize for $name { diff --git a/pkgs/num/src/lib.rs b/pkgs/num/src/lib.rs index 07ba1052..5b0103d4 100644 --- a/pkgs/num/src/lib.rs +++ b/pkgs/num/src/lib.rs @@ -27,6 +27,7 @@ pub mod util; #[doc(hidden)] pub mod __private { + #[cfg(feature = "codec")] pub use bitcoin_consensus_encoding; pub use dash_types; #[cfg(feature = "serde")] diff --git a/pkgs/num/src/util.rs b/pkgs/num/src/util.rs index a19be4ba..02f7ecd8 100644 --- a/pkgs/num/src/util.rs +++ b/pkgs/num/src/util.rs @@ -6,6 +6,27 @@ //! Hash newtype macros. +/// dash-num's [`cfg_codec!`](dash_types::cfg_codec), keyed to `dash-num/codec` +/// (this crate) rather than `dash-types/codec`. +/// +/// `{ .. } else { .. }` picks between two bodies rather than emitting one +/// conditionally, for an item that exists either way. +#[cfg(feature = "codec")] +#[doc(hidden)] +#[macro_export] +macro_rules! cfg_codec { + ({$($with:tt)*} else {$($without:tt)*}) => { $($with)* }; + ($($item:tt)*) => { $($item)* }; +} + +#[cfg(not(feature = "codec"))] +#[doc(hidden)] +#[macro_export] +macro_rules! cfg_codec { + ({$($with:tt)*} else {$($without:tt)*}) => { $($without)* }; + ($($item:tt)*) => {}; +} + /// dash-num's [`cfg_serde!`](dash_types::cfg_serde), keyed to `dash-num/serde` /// (this crate) rather than `dash-types/serde`. #[cfg(feature = "serde")] @@ -25,7 +46,7 @@ macro_rules! cfg_serde { /// Generates `BaseCodec` + `Encode` + `Decode` for hash newtypes. #[macro_export] macro_rules! impl_hash { - ($base:ty, $($name:ident),* $(,)?) => { $( + ($base:ty, $($name:ident),* $(,)?) => { $( $crate::cfg_codec! { impl $crate::__private::dash_types::codec::BaseCodec for $name { fn decode( data: &mut &[u8], @@ -40,7 +61,7 @@ macro_rules! impl_hash { } $crate::__private::dash_types::impl_type!($name); - )* }; + } )* }; } /// Generates a newtype wrapping a hash base type with full trait @@ -52,9 +73,20 @@ macro_rules! make_hash { $(#[$attr:meta])* $name:ident ) => { - $(#[$attr])* - #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, $crate::__private::dash_types::type_id::TypeId)] - pub struct $name($base); + $crate::cfg_codec! { + { + $(#[$attr])* + #[derive( + Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, + $crate::__private::dash_types::type_id::TypeId, + )] + pub struct $name($base); + } else { + $(#[$attr])* + #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] + pub struct $name($base); + } + } $crate::cfg_serde! { impl $crate::__private::serde::Serialize for $name { diff --git a/pkgs/p2p_core/Cargo.toml b/pkgs/p2p_core/Cargo.toml index 4eda80a8..5d700733 100644 --- a/pkgs/p2p_core/Cargo.toml +++ b/pkgs/p2p_core/Cargo.toml @@ -32,7 +32,7 @@ bitcoin-p2p-messages = { workspace = true } bitcoin-primitives = { workspace = true } bitcoin-units = { workspace = true, features = ["alloc"] } cfg-if = "1" -dash-num = { version = "0.0.0", path = "../num" } +dash-num = { version = "0.0.0", path = "../num", features = ["codec"] } dash-pkc = { version = "0.0.0", path = "../pkc", features = ["codec"] } dash-script = { version = "0.0.0", path = "../script" } dash-params = { version = "0.0.0", path = "../params" } diff --git a/pkgs/params/Cargo.toml b/pkgs/params/Cargo.toml index cf49c829..bac1d1c8 100644 --- a/pkgs/params/Cargo.toml +++ b/pkgs/params/Cargo.toml @@ -12,7 +12,7 @@ full = ["std"] [dependencies] bitcoin-primitives = { workspace = true, features = ["alloc"] } bitcoin-units = { workspace = true, features = ["alloc"] } -dash-num = { version = "0.0.0", path = "../num" } +dash-num = { version = "0.0.0", path = "../num", features = ["codec"] } dash-primitives = { version = "0.0.0", path = "../primitives" } dash-script = { version = "0.0.0", path = "../script" } hex-literal = "0.4" diff --git a/pkgs/pkc/Cargo.toml b/pkgs/pkc/Cargo.toml index 7f7b73cf..fd2857a2 100644 --- a/pkgs/pkc/Cargo.toml +++ b/pkgs/pkc/Cargo.toml @@ -9,12 +9,12 @@ aes = { version = "0.8", default-features = false, features = [ "zeroize", ], optional = true } base58ck = { workspace = true, optional = true, features = ["alloc"] } -bitcoin_hashes = { workspace = true, optional = true, features = ["alloc"] } +bitcoin_hashes = { workspace = true, features = ["alloc"] } blst = { version = "0.3", default-features = false, optional = true } ff = { version = "0.14", default-features = false, optional = true } group = { version = "0.14", default-features = false, optional = true } cfg-if = "1" -dash-num = { version = "0.0.0", path = "../num", optional = true } +dash-num = { version = "0.0.0", path = "../num", default-features = false } dash-types = { version = "0.0.0", path = "../types", default-features = false } hex-conservative = { version = "0.3", default-features = false, features = [ "alloc", @@ -49,7 +49,7 @@ serde = { version = "1", features = ["derive"] } [features] default = [] -std = ["base58ck?/std", "bitcoin_hashes?/std", "dash-types/std"] +std = ["base58ck?/std", "bitcoin_hashes/std", "dash-types/std"] bls = [ "dep:aes", "dep:blst", @@ -60,8 +60,7 @@ bls = [ ] codec = [ "dep:base58ck", - "dep:bitcoin_hashes", - "dep:dash-num", + "dash-num/codec", "dash-types/codec", ] ecdsa = ["codec", "dep:k256", "dep:rand_core"] diff --git a/pkgs/pkc/src/bls/dh_bytes.rs b/pkgs/pkc/src/bls/dh_bytes.rs index 4f22dbc5..b512e25a 100644 --- a/pkgs/pkc/src/bls/dh_bytes.rs +++ b/pkgs/pkc/src/bls/dh_bytes.rs @@ -8,57 +8,12 @@ use crate::bls::BlsSchemeId; -use dash_types::derive_sbytes; -use subtle::ConstantTimeEq; -use zeroize::Zeroize; - -use core::marker::PhantomData; +use dash_types::make_sbytes; /// Raw shared secret length (G1 compressed). pub const BLS_DH_LEN: usize = 48; -/// A scheme-tagged Diffie-Hellman shared key, `sk * peer_pk`. -pub struct BlsDhBytes { - inner: [u8; BLS_DH_LEN], - _scheme: PhantomData, -} - -impl BlsDhBytes { - /// Wraps raw bytes. - pub const fn from_bytes(bytes: [u8; BLS_DH_LEN]) -> Self { - Self { - inner: bytes, - _scheme: PhantomData, - } - } - - /// Borrows the inner byte array. - pub const fn as_bytes(&self) -> &[u8; BLS_DH_LEN] { - &self.inner - } -} - -impl Zeroize for BlsDhBytes { - fn zeroize(&mut self) { - self.inner.zeroize(); - } -} - -derive_sbytes!(for[S: BlsSchemeId] BlsDhBytes, BLS_DH_LEN); - -impl Clone for BlsDhBytes { - fn clone(&self) -> Self { - Self { - inner: self.inner, - _scheme: PhantomData, - } - } -} - -impl Eq for BlsDhBytes {} - -impl PartialEq for BlsDhBytes { - fn eq(&self, other: &Self) -> bool { - self.inner.ct_eq(&other.inner).into() - } +make_sbytes! { + /// A scheme-tagged Diffie-Hellman shared key, `sk * peer_pk`. + for[S: BlsSchemeId] BlsDhBytes, BLS_DH_LEN, nocodec } diff --git a/pkgs/pkc/src/bls/ies_bytes.rs b/pkgs/pkc/src/bls/ies_bytes.rs index 21160451..7a34c89c 100644 --- a/pkgs/pkc/src/bls/ies_bytes.rs +++ b/pkgs/pkc/src/bls/ies_bytes.rs @@ -17,11 +17,13 @@ use cfg_if::cfg_if; #[cfg(feature = "codec")] use dash_num::Hash256; #[cfg(feature = "codec")] -use dash_types::codec::{read_bytes, BaseCodec, Checkable, DecodeError, EncodeBuf, Hashable}; +use dash_types::codec::{read_bytes, BaseCodec, DecodeError, EncodeBuf}; #[cfg(feature = "codec")] use dash_types::type_id::TypeId; #[cfg(feature = "codec")] use dash_types::{impl_type, CompactSize}; +#[cfg(feature = "codec")] +use dash_types::{Checkable, Hashable}; use hex_conservative::DisplayHex; use core::fmt; diff --git a/pkgs/pkc/src/bls/public_bytes.rs b/pkgs/pkc/src/bls/public_bytes.rs index 751900eb..4b3c8df8 100644 --- a/pkgs/pkc/src/bls/public_bytes.rs +++ b/pkgs/pkc/src/bls/public_bytes.rs @@ -8,60 +8,23 @@ use crate::bls::BlsSchemeId; -#[cfg(feature = "codec")] use bitcoin_hashes::sha256d::Hash as Sha256d; -#[cfg(feature = "codec")] use dash_num::Hash256; -#[cfg(feature = "codec")] -use dash_types::codec::Hashable; -use dash_types::derive_bytes; -#[cfg(feature = "codec")] -use dash_types::impl_bytes; -#[cfg(feature = "codec")] -use dash_types::type_id::TypeId; - -use core::marker::PhantomData; +use dash_types::make_bytes; +use dash_types::Hashable; /// Raw BLS public key length (G1 compressed). pub const BLS_PK_LEN: usize = 48; -/// Scheme-tagged BLS public key bytes (48 bytes, unvalidated). -#[cfg_attr(feature = "codec", derive(TypeId))] -pub struct BlsPkBytes { - inner: [u8; BLS_PK_LEN], - _scheme: PhantomData, +make_bytes! { + /// Scheme-tagged BLS public key bytes (48 bytes, unvalidated). + for[S: BlsSchemeId] BlsPkBytes, BLS_PK_LEN } -#[cfg(feature = "codec")] -impl_bytes!(for[S: BlsSchemeId] BlsPkBytes, BLS_PK_LEN); - -#[cfg(feature = "codec")] impl Hashable for BlsPkBytes { type Hash = Hash256; fn hash(&self) -> Self::Hash { - Hash256::from_bytes(Sha256d::hash(&self.inner).to_byte_array()) - } -} - -impl BlsPkBytes { - /// Wraps raw bytes. - pub const fn from_bytes(bytes: [u8; BLS_PK_LEN]) -> Self { - Self { - inner: bytes, - _scheme: PhantomData, - } - } - - /// Borrows the inner byte array. - pub const fn as_bytes(&self) -> &[u8; BLS_PK_LEN] { - &self.inner - } - - /// Returns the inner byte array. - pub const fn into_bytes(self) -> [u8; BLS_PK_LEN] { - self.inner + Hash256::from_bytes(Sha256d::hash(self.as_bytes()).to_byte_array()) } } - -derive_bytes!(for[S: BlsSchemeId] BlsPkBytes, BLS_PK_LEN); diff --git a/pkgs/pkc/src/bls/secret_bytes.rs b/pkgs/pkc/src/bls/secret_bytes.rs index 55b13429..98fda7b3 100644 --- a/pkgs/pkc/src/bls/secret_bytes.rs +++ b/pkgs/pkc/src/bls/secret_bytes.rs @@ -8,85 +8,23 @@ use crate::bls::BlsSchemeId; -#[cfg(feature = "codec")] use bitcoin_hashes::sha256d::Hash as Sha256d; -#[cfg(feature = "codec")] use dash_num::Hash256; -#[cfg(feature = "codec")] -use dash_types::codec::Hashable; -use dash_types::derive_sbytes; -#[cfg(feature = "codec")] -use dash_types::impl_sbytes; -#[cfg(feature = "codec")] -use dash_types::type_id::TypeId; -use subtle::ConstantTimeEq; -use zeroize::{Zeroize, Zeroizing}; - -use core::marker::PhantomData; +use dash_types::make_sbytes; +use dash_types::Hashable; /// Raw BLS secret key length (scalar). pub const BLS_SK_LEN: usize = 32; -/// Scheme-tagged BLS secret key bytes (32 bytes, zeroized on drop). -#[cfg_attr(feature = "codec", derive(TypeId))] -pub struct BlsSkBytes { - inner: [u8; BLS_SK_LEN], - _scheme: PhantomData, +make_sbytes! { + /// Scheme-tagged BLS secret key bytes (32 bytes, zeroized on drop). + for[S: BlsSchemeId] BlsSkBytes, BLS_SK_LEN } -#[cfg(feature = "codec")] -impl_sbytes!(for[S: BlsSchemeId] BlsSkBytes, BLS_SK_LEN); - -#[cfg(feature = "codec")] impl Hashable for BlsSkBytes { type Hash = Hash256; fn hash(&self) -> Self::Hash { - Hash256::from_bytes(Sha256d::hash(&self.inner).to_byte_array()) - } -} - -impl BlsSkBytes { - /// Wraps raw bytes. - pub const fn from_bytes(bytes: [u8; BLS_SK_LEN]) -> Self { - Self { - inner: bytes, - _scheme: PhantomData, - } - } - - /// Borrows the inner byte array. - pub const fn as_bytes(&self) -> &[u8; BLS_SK_LEN] { - &self.inner - } - - /// Copies out the inner bytes in a zeroizing wrapper. - pub fn to_bytes(&self) -> Zeroizing<[u8; BLS_SK_LEN]> { - Zeroizing::new(self.inner) - } -} - -impl Zeroize for BlsSkBytes { - fn zeroize(&mut self) { - self.inner.zeroize(); - } -} - -derive_sbytes!(for[S: BlsSchemeId] BlsSkBytes, BLS_SK_LEN); - -impl Clone for BlsSkBytes { - fn clone(&self) -> Self { - Self { - inner: self.inner, - _scheme: PhantomData, - } - } -} - -impl Eq for BlsSkBytes {} - -impl PartialEq for BlsSkBytes { - fn eq(&self, other: &Self) -> bool { - self.inner.ct_eq(&other.inner).into() + Hash256::from_bytes(Sha256d::hash(self.as_bytes()).to_byte_array()) } } diff --git a/pkgs/pkc/src/bls/share_id.rs b/pkgs/pkc/src/bls/share_id.rs index a395c614..83d9f955 100644 --- a/pkgs/pkc/src/bls/share_id.rs +++ b/pkgs/pkc/src/bls/share_id.rs @@ -6,34 +6,12 @@ //! Threshold participant identifier. -use dash_types::derive_bytes; -#[cfg(feature = "codec")] -use dash_types::type_id::Unencodable; +use dash_types::make_bytes; /// Threshold participant identifier length. pub const BLS_ID_LEN: usize = 32; -/// Threshold participant identifier. -#[cfg_attr(feature = "codec", derive(Unencodable))] -pub struct BlsShareId { - inner: [u8; BLS_ID_LEN], +make_bytes! { + /// Threshold participant identifier. + BlsShareId, BLS_ID_LEN, rev, nocodec } - -impl BlsShareId { - /// Wraps raw bytes. - pub const fn from_bytes(bytes: [u8; BLS_ID_LEN]) -> Self { - Self { inner: bytes } - } - - /// Borrows the inner byte array. - pub const fn as_bytes(&self) -> &[u8; BLS_ID_LEN] { - &self.inner - } - - /// Returns the inner byte array. - pub const fn into_bytes(self) -> [u8; BLS_ID_LEN] { - self.inner - } -} - -derive_bytes!(BlsShareId, BLS_ID_LEN, rev); diff --git a/pkgs/pkc/src/bls/sig_bytes.rs b/pkgs/pkc/src/bls/sig_bytes.rs index 201149fb..b4d8feb6 100644 --- a/pkgs/pkc/src/bls/sig_bytes.rs +++ b/pkgs/pkc/src/bls/sig_bytes.rs @@ -8,60 +8,23 @@ use crate::bls::BlsSchemeId; -#[cfg(feature = "codec")] use bitcoin_hashes::sha256d::Hash as Sha256d; -#[cfg(feature = "codec")] use dash_num::Hash256; -#[cfg(feature = "codec")] -use dash_types::codec::Hashable; -use dash_types::derive_bytes; -#[cfg(feature = "codec")] -use dash_types::impl_bytes; -#[cfg(feature = "codec")] -use dash_types::type_id::TypeId; - -use core::marker::PhantomData; +use dash_types::make_bytes; +use dash_types::Hashable; /// Raw BLS signature length (G2 compressed). pub const BLS_SIG_LEN: usize = 96; -/// Scheme-tagged BLS signature bytes (96 bytes, unvalidated). -#[cfg_attr(feature = "codec", derive(TypeId))] -pub struct BlsSigBytes { - inner: [u8; BLS_SIG_LEN], - _scheme: PhantomData, +make_bytes! { + /// Scheme-tagged BLS signature bytes (96 bytes, unvalidated). + for[S: BlsSchemeId] BlsSigBytes, BLS_SIG_LEN } -#[cfg(feature = "codec")] -impl_bytes!(for[S: BlsSchemeId] BlsSigBytes, BLS_SIG_LEN); - -#[cfg(feature = "codec")] impl Hashable for BlsSigBytes { type Hash = Hash256; fn hash(&self) -> Self::Hash { - Hash256::from_bytes(Sha256d::hash(&self.inner).to_byte_array()) - } -} - -impl BlsSigBytes { - /// Wraps raw bytes. - pub const fn from_bytes(bytes: [u8; BLS_SIG_LEN]) -> Self { - Self { - inner: bytes, - _scheme: PhantomData, - } - } - - /// Borrows the inner byte array. - pub const fn as_bytes(&self) -> &[u8; BLS_SIG_LEN] { - &self.inner - } - - /// Returns the inner byte array. - pub const fn into_bytes(self) -> [u8; BLS_SIG_LEN] { - self.inner + Hash256::from_bytes(Sha256d::hash(self.as_bytes()).to_byte_array()) } } - -derive_bytes!(for[S: BlsSchemeId] BlsSigBytes, BLS_SIG_LEN); diff --git a/pkgs/primitives/Cargo.toml b/pkgs/primitives/Cargo.toml index fd2bf232..05b68089 100644 --- a/pkgs/primitives/Cargo.toml +++ b/pkgs/primitives/Cargo.toml @@ -35,7 +35,7 @@ bitcoin-internals = { workspace = true } bitcoin-primitives = { workspace = true, features = ["alloc"] } bitcoin_hashes = { workspace = true, features = ["alloc"] } bitcoin-units = { workspace = true, features = ["alloc"] } -dash-num = { version = "0.0.0", path = "../num" } +dash-num = { version = "0.0.0", path = "../num", features = ["codec"] } dash-pkc = { version = "0.0.0", path = "../pkc", default-features = false, features = [ "codec", ] } diff --git a/pkgs/types/src/codec.rs b/pkgs/types/src/codec.rs index b5376f49..e4399aaf 100644 --- a/pkgs/types/src/codec.rs +++ b/pkgs/types/src/codec.rs @@ -13,6 +13,9 @@ use crate::CompactSize; use core::convert::Infallible; use core::fmt; +// TODO(kwvg): remove compatibility alias +pub use crate::traits::{Checkable, Hashable}; // nosemgrep: use-pub-roots-only + /// Maximum bytes to pre-allocate per batch when deserializing vectors. const MAX_VECTOR_ALLOCATE: usize = 5_000_000; @@ -343,25 +346,6 @@ impl BaseCodec for String { } } -/// Consensus types that have internal consistency checks. -pub trait Checkable { - /// The error type returned on failure. - type Error; - - /// Checks structural invariants, returning the first violation. - #[must_use] - fn check(&self) -> Option; -} - -/// Canonical hashed representation. -pub trait Hashable { - /// The hash output type. - type Hash; - - /// Computes the canonical hash of this value. - fn hash(&self) -> Self::Hash; -} - /// Marker trait for codec coverage enforcement. /// /// Implemented automatically for all `Codec` types via blanket impl, and diff --git a/pkgs/types/src/entity.rs b/pkgs/types/src/entity.rs index 4a7151ac..dd8346ec 100644 --- a/pkgs/types/src/entity.rs +++ b/pkgs/types/src/entity.rs @@ -6,17 +6,24 @@ //! Buffered codec implementation. +#[cfg(feature = "codec")] use crate::codec::DecodeError; +#[cfg(feature = "codec")] use crate::prelude::*; +#[cfg(feature = "codec")] use bitcoin_consensus_encoding::{Decoder, Encoder}; +#[cfg(feature = "codec")] use core::convert::Infallible; +#[cfg(feature = "codec")] use core::fmt; +#[cfg(feature = "codec")] /// Maximum serialized object size (32 MiB). pub const MAX_SER_SIZE: usize = 0x0200_0000; +#[cfg(feature = "codec")] /// An encoder that wraps a pre-built byte vector. #[derive(Clone)] pub struct VecEncoder { @@ -24,6 +31,7 @@ pub struct VecEncoder { done: bool, } +#[cfg(feature = "codec")] impl VecEncoder { /// Creates a new encoder wrapping the given bytes. pub fn new(data: Vec) -> Self { @@ -31,6 +39,7 @@ impl VecEncoder { } } +#[cfg(feature = "codec")] impl fmt::Debug for VecEncoder { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("VecEncoder") @@ -40,6 +49,7 @@ impl fmt::Debug for VecEncoder { } } +#[cfg(feature = "codec")] impl Encoder for VecEncoder { fn current_chunk(&self) -> &[u8] { if self.done { @@ -59,6 +69,7 @@ impl Encoder for VecEncoder { } } +#[cfg(feature = "codec")] /// A decoder that buffers all input and decodes in `end()`. /// /// Wraps types with complex sequential decode logic (conditional fields, @@ -70,6 +81,7 @@ pub struct VecDecoder { decode_fn: fn(&mut &[u8]) -> Result>, } +#[cfg(feature = "codec")] impl VecDecoder { /// Creates a new decoder with the given decode function and /// maximum buffer size. @@ -82,6 +94,7 @@ impl VecDecoder { } } +#[cfg(feature = "codec")] impl fmt::Debug for VecDecoder { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("VecDecoder") @@ -91,6 +104,7 @@ impl fmt::Debug for VecDecoder { } } +#[cfg(feature = "codec")] impl Clone for VecDecoder { fn clone(&self) -> Self { Self { @@ -101,6 +115,7 @@ impl Clone for VecDecoder { } } +#[cfg(feature = "codec")] impl Decoder for VecDecoder { type Output = T; type Error = DecodeError; @@ -137,6 +152,7 @@ impl Decoder for VecDecoder { /// Stages through the growable [`VecEncoder`]/[`VecDecoder`] pair. For /// secret material use [`impl_stype!`](crate::impl_stype) instead, which is /// the same generator over the wiping fixed-width pair. +#[cfg(feature = "codec")] #[macro_export] macro_rules! impl_type { (@parse [$($impl_generics:tt)*] $ty:ty, $max:expr, $err:ty) => { @@ -179,6 +195,7 @@ macro_rules! impl_type { /// /// Staged through the growable [`VecEncoder`]. For a newtype whose contents /// are secret use [`impl_sbytes!`](crate::impl_sbytes). +#[cfg(feature = "codec")] #[macro_export] macro_rules! impl_bytes { // Shared by `impl_bytes!` and `impl_sbytes!`, only the encoder pair differs. @@ -212,44 +229,236 @@ macro_rules! impl_bytes { }; } -/// Declares a fixed-size byte newtype over `[u8; N]` with the `from_bytes` / -/// `to_bytes` / `as_bytes` accessors. +/// The standard trait set for a fixed-size byte newtype, expressed only +/// through `from_bytes` / `as_bytes`. +/// +/// Emits `Clone`, `Copy`, `Default`, `Eq`, `PartialEq`, `Ord`, `PartialOrd`, +/// `Hash`, `is_null`, `AsRef<[u8]>`, `AsRef<[u8; N]>`, `From for +/// [u8; N]`, a hex `Debug`/`Display`, and the hex `serde` pair. /// -/// Invokes [`impl_bytes!`](crate::impl_bytes) and -/// [`derive_bytes!`](crate::derive_bytes). A newtype that needs a validating -/// constructor, a scheme tag, or its own trait set should define itself and -/// invoke those macros manually. +/// A trailing `rev` renders the hex in reverse storage order, the default `fwd` +/// renders storage order. +/// +/// For a newtype holding secrets use [`derive_sbytes!`](crate::derive_sbytes), +/// which withholds everything that would read or copy out the plaintext. #[macro_export] -macro_rules! make_bytes { - ( - $(#[$attr:meta])* - $name:ident, $n:literal - ) => { - $(#[$attr])* - #[derive($crate::type_id::TypeId)] - pub struct $name(pub [u8; $n]); +macro_rules! derive_bytes { + (@parse [$($g:tt)*] $ty:ty, $n:expr, $rev:expr) => { + impl<$($g)*> ::core::clone::Clone for $ty { + fn clone(&self) -> Self { *self } + } + + impl<$($g)*> ::core::marker::Copy for $ty {} + + impl<$($g)*> ::core::default::Default for $ty { + fn default() -> Self { Self::from_bytes([0u8; $n]) } + } + + impl<$($g)*> ::core::cmp::Eq for $ty {} + + impl<$($g)*> ::core::cmp::PartialEq for $ty { + fn eq(&self, other: &Self) -> bool { self.as_bytes() == other.as_bytes() } + } + + impl<$($g)*> ::core::cmp::Ord for $ty { + fn cmp(&self, other: &Self) -> ::core::cmp::Ordering { + self.as_bytes().cmp(other.as_bytes()) + } + } + + impl<$($g)*> ::core::cmp::PartialOrd for $ty { + fn partial_cmp(&self, other: &Self) -> ::core::option::Option<::core::cmp::Ordering> { + ::core::option::Option::Some(::core::cmp::Ord::cmp(self, other)) + } + } - $crate::impl_bytes!($name, $n); + impl<$($g)*> ::core::hash::Hash for $ty { + fn hash(&self, state: &mut H) { + ::core::hash::Hash::hash(self.as_bytes(), state); + } + } + + impl<$($g)*> ::core::convert::AsRef<[u8]> for $ty { + fn as_ref(&self) -> &[u8] { self.as_bytes() } + } + + impl<$($g)*> ::core::convert::AsRef<[u8; $n]> for $ty { + fn as_ref(&self) -> &[u8; $n] { self.as_bytes() } + } + + impl<$($g)*> ::core::convert::From<$ty> for [u8; $n] { + fn from(val: $ty) -> Self { *val.as_bytes() } + } + + impl<$($g)*> $ty { + /// Returns `true` when every byte is zero. + pub fn is_null(&self) -> bool { self.as_bytes().iter().all(|&b| b == 0) } + } - $crate::derive_bytes!($name, $n); + impl<$($g)*> ::core::fmt::Debug for $ty { + fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { + $crate::qtypestr(f, ::core::any::type_name::())?; + f.write_str("(")?; + ::core::fmt::Display::fmt(self, f)?; + f.write_str(")") + } + } + + $crate::derive_bytes!(@hex [$($g)*] $ty, $n, $rev); + }; + (@order [$($g:tt)*] $ty:ty, $n:expr, fwd) => { + $crate::derive_bytes!(@parse [$($g)*] $ty, $n, false); + }; + (@order [$($g:tt)*] $ty:ty, $n:expr, rev) => { + $crate::derive_bytes!(@parse [$($g)*] $ty, $n, true); + }; + (@hex [$($g:tt)*] $ty:ty, $n:expr, $rev:expr) => { + impl<$($g)*> ::core::fmt::Display for $ty { + fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { + let bytes = self.as_bytes(); + for i in 0..$n { + let byte = if $rev { bytes[$n - 1 - i] } else { bytes[i] }; + ::core::write!(f, "{byte:02x}")?; + } + ::core::result::Result::Ok(()) + } + } - impl $name { + $crate::cfg_serde! { + impl<$($g)*> $crate::__private::serde::Serialize for $ty { + fn serialize(&self, serializer: Z) -> Result + where + Z: $crate::__private::serde::Serializer, + { + serializer.serialize_str(&::alloc::format!("{self}")) + } + } + + impl<'de, $($g)*> $crate::__private::serde::Deserialize<'de> for $ty { + fn deserialize(deserializer: D) -> Result + where + D: $crate::__private::serde::Deserializer<'de>, + { + use $crate::__private::serde::de::Error as _; + let s = <::alloc::string::String as $crate::__private::serde::Deserialize>::deserialize(deserializer)?; + let mut bytes = <[u8; $n] as $crate::__private::hex_conservative::FromHex>::from_hex(&s) + .map_err(D::Error::custom)?; + if $rev { + bytes.reverse(); + } + ::core::result::Result::Ok(Self::from_bytes(bytes)) + } + } + } + }; + (for[$($generic:tt)*] $ty:ty, $n:expr, $order:tt) => { + $crate::derive_bytes!(@order [$($generic)*] $ty, $n, $order); + }; + (for[$($generic:tt)*] $ty:ty, $n:expr) => { + $crate::derive_bytes!(@order [$($generic)*] $ty, $n, fwd); + }; + ($ty:ty, $n:expr, $order:tt) => { + $crate::derive_bytes!(@order [] $ty, $n, $order); + }; + ($ty:ty, $n:expr) => { + $crate::derive_bytes!(@order [] $ty, $n, fwd); + }; +} + +/// Declares a fixed-size byte newtype over `[u8; N]` with the `from_bytes` / +/// `to_bytes` / `as_bytes` accessors. +/// +/// A `for[..]` prefix takes type parameters, held in a `PhantomData` beside +/// the bytes, for a type that utilizes parameters for tagging without mutating +/// the inner structure. +/// +/// Two optional trailing words follow the width. The first is the hex order, +/// `fwd` (the default) or `rev`. The second is whether the bag carries a wire +/// image, `codec` (the default) or `nocodec`; the second cannot be given +/// without the first. +/// +/// Invokes `impl_bytes!` and [`derive_bytes!`](crate::derive_bytes). A newtype +/// that needs a validating constructor or its own trait set should define +/// itself and invoke those macros manually. +#[macro_export] +macro_rules! make_bytes { + // `@struct`, `@decl` and `@accessors` are shared with `make_sbytes!` + (@struct [$($g:tt)*] {$($attr:tt)*} $(#[$derive:meta])? $name:ident $(<$($param:ident),+>)?, $n:expr) => { + $($attr)* + $(#[$derive])? + // A plain bag gets an empty `<>`, legal and invisible in rustdoc. + pub struct $name<$($g)*> { + inner: [u8; $n], + $(_marker: ::core::marker::PhantomData ($($param,)+)>,)? + } + }; + (@decl [$($g:tt)*] $attrs:tt $name:ident $(<$($param:ident),+>)?, $n:expr, codec, $codec:ident) => { + $crate::cfg_codec! { + { + $crate::make_bytes!( + @struct [$($g)*] $attrs #[derive($crate::type_id::TypeId)] $name $(<$($param),+>)?, $n + ); + + $crate::$codec!(@parse [$($g)*] $name $(<$($param),+>)?, $n); + } else { + $crate::make_bytes!(@struct [$($g)*] $attrs $name $(<$($param),+>)?, $n); + } + } + }; + // `$codec` is accepted for symmetry with the arm above, nothing to stage. + (@decl [$($g:tt)*] $attrs:tt $name:ident $(<$($param:ident),+>)?, $n:expr, nocodec, $codec:ident) => { + $crate::cfg_codec! { + { + $crate::make_bytes!( + @struct [$($g)*] $attrs #[derive($crate::type_id::Unencodable)] $name $(<$($param),+>)?, $n + ); + } else { + $crate::make_bytes!(@struct [$($g)*] $attrs $name $(<$($param),+>)?, $n); + } + } + }; + (@accessors [$($g:tt)*] $name:ident $(<$($param:ident),+>)?, $n:expr, {$($to_bytes:tt)*}) => { + impl<$($g)*> $name $(<$($param),+>)? { /// Wraps raw bytes without validation. pub const fn from_bytes(bytes: [u8; $n]) -> Self { - Self(bytes) + Self { + inner: bytes, + $(_marker: ::core::marker::PhantomData:: ($($param,)+)>,)? + } } - /// Returns the inner byte array. - pub const fn to_bytes(self) -> [u8; $n] { - self.0 - } + $($to_bytes)* /// Borrows the inner byte array. pub const fn as_bytes(&self) -> &[u8; $n] { - &self.0 + &self.inner } } }; + (@parse [$($g:tt)*] $attrs:tt $name:ident $(<$($param:ident),+>)?, $n:expr, $rev:tt, $enc:ident) => { + $crate::make_bytes!(@decl [$($g)*] $attrs $name $(<$($param),+>)?, $n, $enc, impl_bytes); + + $crate::derive_bytes!(@order [$($g)*] $name $(<$($param),+>)?, $n, $rev); + + $crate::make_bytes!(@accessors [$($g)*] $name $(<$($param),+>)?, $n, { + /// Copies out the inner byte array. + pub const fn to_bytes(&self) -> [u8; $n] { + self.inner + } + }); + }; + (@parse [$($g:tt)*] $attrs:tt $name:ident $(<$($param:ident),+>)?, $n:expr, $rev:tt) => { + $crate::make_bytes!(@parse [$($g)*] $attrs $name $(<$($param),+>)?, $n, $rev, codec); + }; + (@parse [$($g:tt)*] $attrs:tt $name:ident $(<$($param:ident),+>)?, $n:expr) => { + $crate::make_bytes!(@parse [$($g)*] $attrs $name $(<$($param),+>)?, $n, fwd); + }; + ($(#[$attr:meta])* for[$($generic:tt)*] $name:ident<$($param:ident),+>, $($args:tt)*) => { + $crate::make_bytes!(@parse [$($generic)*] {$(#[$attr])*} $name<$($param),+>, $($args)*); + }; + ($(#[$attr:meta])* $name:ident, $($args:tt)*) => { + $crate::make_bytes!(@parse [] {$(#[$attr])*} $name, $($args)*); + }; } /// Delegates `BaseCodec`, `Hashable`, and `impl_type!` through another type. @@ -262,6 +471,7 @@ macro_rules! make_bytes { /// `$max` bounds the `impl_type!` decoder buffer to the wrapped type's own /// maximum encoded length. For a secret wire image use /// [`dlgt_scodec!`](crate::dlgt_scodec). +#[cfg(feature = "codec")] #[macro_export] macro_rules! dlgt_codec { // Shared by `dlgt_codec!` and `dlgt_scodec!`, only the encoder pair differs. diff --git a/pkgs/types/src/lib.rs b/pkgs/types/src/lib.rs index fee945cc..7e1a83d7 100644 --- a/pkgs/types/src/lib.rs +++ b/pkgs/types/src/lib.rs @@ -13,27 +13,25 @@ extern crate self as dash_types; #[cfg(feature = "std")] extern crate std; -#[allow(unused_macros, reason = "used by feature-gated submodules")] -#[cfg(feature = "codec")] -mod adapters; -#[cfg(feature = "codec")] mod entity; mod macros; #[allow(unused_imports, reason = "ergonomic shim, exports may be unused")] mod prelude; -#[cfg(feature = "codec")] mod secret; -#[cfg(feature = "codec")] -mod uint; +mod traits; #[cfg(feature = "serde")] pub mod serialize; pub use macros::qtypestr; +pub use traits::{Checkable, Hashable}; cfg_if::cfg_if! { if #[cfg(feature = "codec")] { + #[allow(unused_macros, reason = "used by feature-gated submodules")] + mod adapters; mod compact; + mod uint; pub mod codec; pub mod type_id; diff --git a/pkgs/types/src/macros.rs b/pkgs/types/src/macros.rs index b9501855..2cdc3334 100644 --- a/pkgs/types/src/macros.rs +++ b/pkgs/types/src/macros.rs @@ -14,6 +14,10 @@ use core::fmt; /// against the invoking crate, which doesn't need have a `codec` feature at /// all. This marker is compiled here, so it tracks `dash-types` instead. /// +/// `{ .. } else { .. }` picks between two bodies rather than emitting one +/// conditionally, for an item that exists either way but is built differently +/// with the feature enabled. +/// /// The two arms must stay plain `#[cfg]` items. Wrapping them in `cfg_if!` /// makes the definition macro-expanded, and a macro-expanded `#[macro_export]` /// macro cannot be reached by `$crate::` from its own crate (rust#52234). @@ -21,6 +25,7 @@ use core::fmt; #[doc(hidden)] #[macro_export] macro_rules! cfg_codec { + ({$($with:tt)*} else {$($without:tt)*}) => { $($with)* }; ($($item:tt)*) => { $($item)* }; } @@ -28,6 +33,7 @@ macro_rules! cfg_codec { #[doc(hidden)] #[macro_export] macro_rules! cfg_codec { + ({$($with:tt)*} else {$($without:tt)*}) => { $($without)* }; ($($item:tt)*) => {}; } @@ -351,202 +357,6 @@ macro_rules! enum_map { }; } -/// The standard trait set for a fixed-size byte newtype, expressed only -/// through `from_bytes` / `as_bytes`. -/// -/// Emits `Clone`, `Copy`, `Default`, `Eq`, `PartialEq`, `Ord`, `PartialOrd`, -/// `Hash`, `is_null`, `AsRef<[u8]>`, `AsRef<[u8; N]>`, `From for -/// [u8; N]`, a hex `Debug`/`Display`, and the hex `serde` pair. -/// -/// A trailing `rev` renders the hex in reverse storage order, the default `fwd` -/// renders storage order. -/// -/// For a newtype holding secrets use [`derive_sbytes!`](crate::derive_sbytes), -/// which withholds everything that would read or copy out the plaintext. -#[macro_export] -macro_rules! derive_bytes { - (@parse [$($g:tt)*] $ty:ty, $n:expr, $rev:expr) => { - impl<$($g)*> ::core::clone::Clone for $ty { - fn clone(&self) -> Self { *self } - } - - impl<$($g)*> ::core::marker::Copy for $ty {} - - impl<$($g)*> ::core::default::Default for $ty { - fn default() -> Self { Self::from_bytes([0u8; $n]) } - } - - impl<$($g)*> ::core::cmp::Eq for $ty {} - - impl<$($g)*> ::core::cmp::PartialEq for $ty { - fn eq(&self, other: &Self) -> bool { self.as_bytes() == other.as_bytes() } - } - - impl<$($g)*> ::core::cmp::Ord for $ty { - fn cmp(&self, other: &Self) -> ::core::cmp::Ordering { - self.as_bytes().cmp(other.as_bytes()) - } - } - - impl<$($g)*> ::core::cmp::PartialOrd for $ty { - fn partial_cmp(&self, other: &Self) -> ::core::option::Option<::core::cmp::Ordering> { - ::core::option::Option::Some(::core::cmp::Ord::cmp(self, other)) - } - } - - impl<$($g)*> ::core::hash::Hash for $ty { - fn hash(&self, state: &mut H) { - ::core::hash::Hash::hash(self.as_bytes(), state); - } - } - - impl<$($g)*> ::core::convert::AsRef<[u8]> for $ty { - fn as_ref(&self) -> &[u8] { self.as_bytes() } - } - - impl<$($g)*> ::core::convert::AsRef<[u8; $n]> for $ty { - fn as_ref(&self) -> &[u8; $n] { self.as_bytes() } - } - - impl<$($g)*> ::core::convert::From<$ty> for [u8; $n] { - fn from(val: $ty) -> Self { *val.as_bytes() } - } - - impl<$($g)*> $ty { - /// Returns `true` when every byte is zero. - pub fn is_null(&self) -> bool { self.as_bytes().iter().all(|&b| b == 0) } - } - - impl<$($g)*> ::core::fmt::Debug for $ty { - fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { - $crate::qtypestr(f, ::core::any::type_name::())?; - f.write_str("(")?; - ::core::fmt::Display::fmt(self, f)?; - f.write_str(")") - } - } - - $crate::derive_bytes!(@hex [$($g)*] $ty, $n, $rev); - }; - (@order [$($g:tt)*] $ty:ty, $n:expr, fwd) => { - $crate::derive_bytes!(@parse [$($g)*] $ty, $n, false); - }; - (@order [$($g:tt)*] $ty:ty, $n:expr, rev) => { - $crate::derive_bytes!(@parse [$($g)*] $ty, $n, true); - }; - (@hex [$($g:tt)*] $ty:ty, $n:expr, $rev:expr) => { - impl<$($g)*> ::core::fmt::Display for $ty { - fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { - let bytes = self.as_bytes(); - for i in 0..$n { - let byte = if $rev { bytes[$n - 1 - i] } else { bytes[i] }; - ::core::write!(f, "{byte:02x}")?; - } - ::core::result::Result::Ok(()) - } - } - - $crate::cfg_serde! { - impl<$($g)*> $crate::__private::serde::Serialize for $ty { - fn serialize(&self, serializer: Z) -> Result - where - Z: $crate::__private::serde::Serializer, - { - serializer.serialize_str(&::alloc::format!("{self}")) - } - } - - impl<'de, $($g)*> $crate::__private::serde::Deserialize<'de> for $ty { - fn deserialize(deserializer: D) -> Result - where - D: $crate::__private::serde::Deserializer<'de>, - { - use $crate::__private::serde::de::Error as _; - let s = <::alloc::string::String as $crate::__private::serde::Deserialize>::deserialize(deserializer)?; - let mut bytes = <[u8; $n] as $crate::__private::hex_conservative::FromHex>::from_hex(&s) - .map_err(D::Error::custom)?; - if $rev { - bytes.reverse(); - } - ::core::result::Result::Ok(Self::from_bytes(bytes)) - } - } - } - }; - (for[$($generic:tt)*] $ty:ty, $n:expr, $order:tt) => { - $crate::derive_bytes!(@order [$($generic)*] $ty, $n, $order); - }; - (for[$($generic:tt)*] $ty:ty, $n:expr) => { - $crate::derive_bytes!(@order [$($generic)*] $ty, $n, fwd); - }; - ($ty:ty, $n:expr, $order:tt) => { - $crate::derive_bytes!(@order [] $ty, $n, $order); - }; - ($ty:ty, $n:expr) => { - $crate::derive_bytes!(@order [] $ty, $n, fwd); - }; -} - -/// The secret counterpart to [`derive_bytes!`](crate::derive_bytes), for a -/// fixed-size byte newtype holding key material. -/// -/// Emits `Drop`, `ZeroizeOnDrop`, `is_null`, the `AsRef` pair, and a redacting -/// `Debug`/`Display`. `Zeroize`, `Clone` and `Eq`/`PartialEq` are left to the -/// type: only it knows which fields are secret, and equality must be -/// constant-time. -/// -/// Withholds `Copy`, `Default`, `Ord`/`PartialOrd`/`Hash`, `From for -/// [u8; N]` and the hex `serde` pair, each because it either escapes the wipe -/// or reads the plaintext. Do *not* implement them. -#[macro_export] -macro_rules! derive_sbytes { - (@parse [$($g:tt)*] $ty:ty, $n:expr) => { - impl<$($g)*> ::core::ops::Drop for $ty { - fn drop(&mut self) { - ::zeroize(self); - } - } - - impl<$($g)*> $crate::__private::zeroize::ZeroizeOnDrop for $ty {} - - impl<$($g)*> $ty { - /// Returns `true` when every byte is zero. - pub fn is_null(&self) -> bool { - use $crate::__private::subtle::ConstantTimeEq as _; - self.as_bytes().ct_eq(&[0u8; $n]).into() - } - } - - impl<$($g)*> ::core::fmt::Debug for $ty { - fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { - // `type_name` rather than `stringify!`, which cannot see the generics - $crate::qtypestr(f, ::core::any::type_name::())?; - f.write_str("(..)") - } - } - - impl<$($g)*> ::core::fmt::Display for $ty { - fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { - ::core::fmt::Debug::fmt(self, f) - } - } - - impl<$($g)*> ::core::convert::AsRef<[u8]> for $ty { - fn as_ref(&self) -> &[u8] { self.as_bytes() } - } - - impl<$($g)*> ::core::convert::AsRef<[u8; $n]> for $ty { - fn as_ref(&self) -> &[u8; $n] { self.as_bytes() } - } - }; - (for[$($generic:tt)*] $($args:tt)*) => { - $crate::derive_sbytes!(@parse [$($generic)*] $($args)*); - }; - ($($args:tt)*) => { - $crate::derive_sbytes!(@parse [] $($args)*); - }; -} - /// Generates `From` + `From<&T>` (or `TryFrom` equivalents). The closure /// body receives `&$src`; the owned impl delegates. #[macro_export] diff --git a/pkgs/types/src/secret.rs b/pkgs/types/src/secret.rs index 55ae7ea6..ae54de9b 100644 --- a/pkgs/types/src/secret.rs +++ b/pkgs/types/src/secret.rs @@ -6,17 +6,24 @@ //! Secret-holding codec implementation. +#[cfg(feature = "codec")] use crate::codec::{DecodeError, EncodeBuf}; +#[cfg(feature = "codec")] use bitcoin_consensus_encoding::{Decoder, Encoder}; +#[cfg(feature = "codec")] use zeroize::Zeroize; +#[cfg(feature = "codec")] use core::convert::Infallible; +#[cfg(feature = "codec")] use core::fmt; +#[cfg(feature = "codec")] /// Widest buffer [`ArrEncoder`] and [`ArrDecoder`] will wipe. pub const MAX_ARR_SIZE: usize = 512; +#[cfg(feature = "codec")] /// Fixed-size encode buffer backed by `[u8; N]`. /// /// Implements [`Zeroize`] but has no `Drop`, so it does *not* wipe itself when @@ -33,6 +40,7 @@ pub struct ArrayBuf { len: usize, } +#[cfg(feature = "codec")] impl ArrayBuf { /// Creates an empty buffer. pub const fn new() -> Self { @@ -70,18 +78,21 @@ impl ArrayBuf { } } +#[cfg(feature = "codec")] impl fmt::Debug for ArrayBuf { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("ArrayBuf").field("len", &self.len).finish() } } +#[cfg(feature = "codec")] impl Default for ArrayBuf { fn default() -> Self { Self::new() } } +#[cfg(feature = "codec")] impl EncodeBuf for ArrayBuf { fn push(&mut self, byte: u8) { self.buf[self.len] = byte; @@ -94,6 +105,7 @@ impl EncodeBuf for ArrayBuf { } } +#[cfg(feature = "codec")] impl Zeroize for ArrayBuf { fn zeroize(&mut self) { self.buf.zeroize(); @@ -101,6 +113,7 @@ impl Zeroize for ArrayBuf { } } +#[cfg(feature = "codec")] /// An encoder for values whose encoded width is bounded at compile time. /// /// Costs a byte-wise volatile write per byte of `N`, so it suits key material @@ -111,6 +124,7 @@ pub struct ArrEncoder { done: bool, } +#[cfg(feature = "codec")] impl ArrEncoder { /// Wraps a filled buffer. /// @@ -121,6 +135,7 @@ impl ArrEncoder { } } +#[cfg(feature = "codec")] impl fmt::Debug for ArrEncoder { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("ArrEncoder") @@ -130,12 +145,14 @@ impl fmt::Debug for ArrEncoder { } } +#[cfg(feature = "codec")] impl Drop for ArrEncoder { fn drop(&mut self) { self.data.zeroize(); } } +#[cfg(feature = "codec")] impl Encoder for ArrEncoder { fn current_chunk(&self) -> &[u8] { if self.done { @@ -155,12 +172,14 @@ impl Encoder for ArrEncoder { } } +#[cfg(feature = "codec")] /// A decoder for values whose encoded width is bounded by `N`. pub struct ArrDecoder { buf: ArrayBuf, decode_fn: fn(&mut &[u8]) -> Result>, } +#[cfg(feature = "codec")] impl ArrDecoder { /// Creates a decoder that accepts at most `N` bytes. /// @@ -174,6 +193,7 @@ impl ArrDecoder { } } +#[cfg(feature = "codec")] impl fmt::Debug for ArrDecoder { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("ArrDecoder") @@ -183,12 +203,14 @@ impl fmt::Debug for ArrDecoder { } } +#[cfg(feature = "codec")] impl Drop for ArrDecoder { fn drop(&mut self) { self.buf.zeroize(); } } +#[cfg(feature = "codec")] impl Decoder for ArrDecoder { type Output = T; type Error = DecodeError; @@ -229,6 +251,7 @@ impl Decoder for ArrDecoder { /// `$n` and capped at [`MAX_ARR_SIZE`]. For public material use /// [`impl_type!`](crate::impl_type), the same generator over the growable /// pair. +#[cfg(feature = "codec")] #[macro_export] macro_rules! impl_stype { (@parse [$($impl_generics:tt)*] $ty:ty, $n:expr, $err:ty) => { @@ -273,6 +296,7 @@ macro_rules! impl_stype { /// /// Same `BaseCodec` and `From<[u8; N]>`, staged through the wiping /// [`ArrEncoder`] rather than the growable [`VecEncoder`](crate::VecEncoder). +#[cfg(feature = "codec")] #[macro_export] macro_rules! impl_sbytes { (@parse [$($g:tt)*] $ty:ty, $n:expr) => { @@ -288,6 +312,124 @@ macro_rules! impl_sbytes { }; } +/// The secret counterpart to [`derive_bytes!`](crate::derive_bytes), for a +/// fixed-size byte newtype holding key material. +/// +/// Emits `Drop`, `ZeroizeOnDrop`, `is_null`, the `AsRef` pair, and a redacting +/// `Debug`/`Display`. `Zeroize`, `Clone` and `Eq`/`PartialEq` are left to the +/// type: only it knows which fields are secret, and equality must be +/// constant-time. +/// +/// Withholds `Copy`, `Default`, `Ord`/`PartialOrd`/`Hash`, `From for +/// [u8; N]` and the hex `serde` pair, each because it either escapes the wipe +/// or reads the plaintext. Do *not* implement them. +#[macro_export] +macro_rules! derive_sbytes { + (@parse [$($g:tt)*] $ty:ty, $n:expr) => { + impl<$($g)*> ::core::ops::Drop for $ty { + fn drop(&mut self) { + ::zeroize(self); + } + } + + impl<$($g)*> $crate::__private::zeroize::ZeroizeOnDrop for $ty {} + + impl<$($g)*> $ty { + /// Returns `true` when every byte is zero. + pub fn is_null(&self) -> bool { + use $crate::__private::subtle::ConstantTimeEq as _; + self.as_bytes().ct_eq(&[0u8; $n]).into() + } + } + + impl<$($g)*> ::core::fmt::Debug for $ty { + fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { + // `type_name` rather than `stringify!`, which cannot see the generics + $crate::qtypestr(f, ::core::any::type_name::())?; + f.write_str("(..)") + } + } + + impl<$($g)*> ::core::fmt::Display for $ty { + fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { + ::core::fmt::Debug::fmt(self, f) + } + } + + impl<$($g)*> ::core::convert::AsRef<[u8]> for $ty { + fn as_ref(&self) -> &[u8] { self.as_bytes() } + } + + impl<$($g)*> ::core::convert::AsRef<[u8; $n]> for $ty { + fn as_ref(&self) -> &[u8; $n] { self.as_bytes() } + } + }; + (for[$($generic:tt)*] $($args:tt)*) => { + $crate::derive_sbytes!(@parse [$($generic)*] $($args)*); + }; + ($($args:tt)*) => { + $crate::derive_sbytes!(@parse [] $($args)*); + }; +} + +/// Declares a fixed-size secret byte newtype over `[u8; N]`, the secret +/// counterpart to [`make_bytes!`](crate::make_bytes). +/// +/// A `for[..]` prefix takes type parameters, held in a `PhantomData` beside +/// the bytes, for a type that utilizes parameters for tagging without mutating +/// the inner structure. +/// +/// An optional trailing word follows the width. Whether the bag carries a +/// wire image, `codec` (the default) or `nocodec`. +/// +/// Invokes `impl_sbytes!` and [`derive_sbytes!`](crate::derive_sbytes). A +/// newtype that needs a validating constructor or its own trait set should +/// define itself and invoke those macros manually. +#[macro_export] +macro_rules! make_sbytes { + (@parse [$($g:tt)*] $attrs:tt $name:ident $(<$($param:ident),+>)?, $n:expr, $enc:ident) => { + $crate::make_bytes!(@decl [$($g)*] $attrs $name $(<$($param),+>)?, $n, $enc, impl_sbytes); + + $crate::derive_sbytes!(@parse [$($g)*] $name $(<$($param),+>)?, $n); + + $crate::make_bytes!(@accessors [$($g)*] $name $(<$($param),+>)?, $n, { + /// Copies out the inner byte array. + pub fn to_bytes(&self) -> $crate::__private::zeroize::Zeroizing<[u8; $n]> { + $crate::__private::zeroize::Zeroizing::new(self.inner) + } + }); + + impl<$($g)*> $crate::__private::zeroize::Zeroize for $name $(<$($param),+>)? { + fn zeroize(&mut self) { + $crate::__private::zeroize::Zeroize::zeroize(&mut self.inner); + } + } + + impl<$($g)*> ::core::clone::Clone for $name $(<$($param),+>)? { + fn clone(&self) -> Self { + Self::from_bytes(self.inner) + } + } + + impl<$($g)*> ::core::cmp::Eq for $name $(<$($param),+>)? {} + + impl<$($g)*> ::core::cmp::PartialEq for $name $(<$($param),+>)? { + fn eq(&self, other: &Self) -> bool { + $crate::__private::subtle::ConstantTimeEq::ct_eq(&self.inner[..], &other.inner[..]).into() + } + } + }; + (@parse [$($g:tt)*] $attrs:tt $name:ident $(<$($param:ident),+>)?, $n:expr) => { + $crate::make_sbytes!(@parse [$($g)*] $attrs $name $(<$($param),+>)?, $n, codec); + }; + ($(#[$attr:meta])* for[$($generic:tt)*] $name:ident<$($param:ident),+>, $($args:tt)*) => { + $crate::make_sbytes!(@parse [$($generic)*] {$(#[$attr])*} $name<$($param),+>, $($args)*); + }; + ($(#[$attr:meta])* $name:ident, $($args:tt)*) => { + $crate::make_sbytes!(@parse [] {$(#[$attr])*} $name, $($args)*); + }; +} + /// The secret counterpart to [`dlgt_codec!`](crate::dlgt_codec), for an /// operational type whose wire image is key material. /// @@ -298,6 +440,7 @@ macro_rules! impl_sbytes { /// `$n` bounds the encoded width rather than fixing it: the staging buffer is /// an [`ArrayBuf<$n>`](crate::ArrayBuf), so a narrower image is emitted as /// written and a wider one panics on the overflowing write. +#[cfg(feature = "codec")] #[macro_export] macro_rules! dlgt_scodec { (@parse [$($impl_generics:tt)*] $ops:ty => $bytes:ty, $hash:ty, $err:ty, $n:expr) => { @@ -313,7 +456,7 @@ macro_rules! dlgt_scodec { }; } -#[cfg(test)] +#[cfg(all(test, feature = "codec"))] mod tests { use super::{ArrDecoder, ArrEncoder, ArrayBuf, MAX_ARR_SIZE}; use crate::codec::{DecodeError, EncodeBuf}; diff --git a/pkgs/types/src/traits.rs b/pkgs/types/src/traits.rs new file mode 100644 index 00000000..2abd4501 --- /dev/null +++ b/pkgs/types/src/traits.rs @@ -0,0 +1,26 @@ +// +// Copyright (c) 2026-present, The Dash Core developers +// SPDX-License-Identifier: MIT +// See the accompanying file LICENSE or https://opensource.org/license/MIT +// + +//! Shared trait definitions. + +/// Consensus types that have internal consistency checks. +pub trait Checkable { + /// The error type returned on failure. + type Error; + + /// Checks structural invariants, returning the first violation. + #[must_use] + fn check(&self) -> Option; +} + +/// Canonical hashed representation. +pub trait Hashable { + /// The hash output type. + type Hash; + + /// Computes the canonical hash of this value. + fn hash(&self) -> Self::Hash; +}