diff --git a/Cargo-minimal.lock b/Cargo-minimal.lock index a6dd5ab4a..53a1cb9d5 100644 --- a/Cargo-minimal.lock +++ b/Cargo-minimal.lock @@ -110,6 +110,7 @@ dependencies = [ name = "bitcoin-primitives" version = "0.100.0" dependencies = [ + "arbitrary", "bitcoin-internals", "bitcoin-io", "bitcoin-units", diff --git a/Cargo-recent.lock b/Cargo-recent.lock index 2be0c9915..3bd3f7acb 100644 --- a/Cargo-recent.lock +++ b/Cargo-recent.lock @@ -109,6 +109,7 @@ dependencies = [ name = "bitcoin-primitives" version = "0.100.0" dependencies = [ + "arbitrary", "bitcoin-internals", "bitcoin-io", "bitcoin-units", diff --git a/bitcoin/Cargo.toml b/bitcoin/Cargo.toml index d73832d57..9805b2a4d 100644 --- a/bitcoin/Cargo.toml +++ b/bitcoin/Cargo.toml @@ -22,7 +22,7 @@ rand = ["secp256k1/rand"] serde = ["dep:serde", "hashes/serde", "internals/serde", "primitives/serde", "secp256k1/serde", "units/serde"] secp-lowmemory = ["secp256k1/lowmemory"] secp-recovery = ["secp256k1/recovery"] -arbitrary = ["dep:arbitrary", "units/arbitrary"] +arbitrary = ["dep:arbitrary", "units/arbitrary", "primitives/arbitrary"] [dependencies] base58 = { package = "base58ck", version = "0.1.0", default-features = false, features = ["alloc"] } diff --git a/bitcoin/src/blockdata/transaction.rs b/bitcoin/src/blockdata/transaction.rs index 174afff09..d7b65fbf7 100644 --- a/bitcoin/src/blockdata/transaction.rs +++ b/bitcoin/src/blockdata/transaction.rs @@ -1409,6 +1409,42 @@ impl InputWeightPrediction { } } +#[cfg(feature = "arbitrary")] +impl<'a> Arbitrary<'a> for OutPoint { + fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result { + Ok(OutPoint{ + txid: Txid::arbitrary(u)?, + vout: u32::arbitrary(u)? + }) + } +} + +#[cfg(feature = "arbitrary")] +impl<'a> Arbitrary<'a> for TxIn { + fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result { + Ok(TxIn{ + previous_output: OutPoint::arbitrary(u)?, + script_sig: ScriptBuf::arbitrary(u)?, + sequence: Sequence::arbitrary(u)?, + witness: Witness::arbitrary(u)? + }) + } +} + +#[cfg(feature = "arbitrary")] +impl<'a> Arbitrary<'a> for Transaction { + fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result { + use primitives::absolute::LockTime; + + Ok(Transaction { + version: Version::arbitrary(u)?, + lock_time: LockTime::arbitrary(u)?, + input: Vec::::arbitrary(u)?, + output: Vec::::arbitrary(u)? + }) + } +} + #[cfg(test)] mod tests { use hex::{test_hex_unwrap as hex, FromHex}; diff --git a/bitcoin/src/blockdata/witness.rs b/bitcoin/src/blockdata/witness.rs index 98a5c4771..705559316 100644 --- a/bitcoin/src/blockdata/witness.rs +++ b/bitcoin/src/blockdata/witness.rs @@ -18,6 +18,8 @@ use crate::prelude::Vec; use crate::script::ScriptExt as _; use crate::taproot::{self, TAPROOT_ANNEX_PREFIX}; use crate::{Script, VarInt}; +#[cfg(feature = "arbitrary")] +use arbitrary::{Arbitrary, Unstructured}; /// The Witness is the data used to unlock bitcoin since the [segwit upgrade]. /// @@ -618,6 +620,14 @@ impl Default for Witness { fn default() -> Self { Self::new() } } +#[cfg(feature = "arbitrary")] +impl<'a> Arbitrary<'a> for Witness { + fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result { + let arbitrary_bytes = Vec::>::arbitrary(u)?; + Ok(Witness::from_slice(&arbitrary_bytes)) + } +} + #[cfg(test)] mod test { use hex::test_hex_unwrap as hex; diff --git a/primitives/Cargo.toml b/primitives/Cargo.toml index 2d4007d39..7cc37e784 100644 --- a/primitives/Cargo.toml +++ b/primitives/Cargo.toml @@ -19,8 +19,10 @@ default = ["std"] std = ["alloc", "hashes/std", "internals/std", "io/std", "units/std"] alloc = ["hashes/alloc", "internals/alloc", "io/alloc", "units/alloc"] serde = ["dep:serde", "hashes/serde", "internals/serde", "units/serde", "alloc"] +arbitrary = ["dep:arbitrary", "units/arbitrary"] [dependencies] +arbitrary = { version = "1", optional = true } hashes = { package = "bitcoin_hashes", version = "0.14.0", default-features = false, features = ["bitcoin-io"] } internals = { package = "bitcoin-internals", version = "0.3.0" } io = { package = "bitcoin-io", version = "0.1.1", default-features = false } diff --git a/primitives/contrib/test_vars.sh b/primitives/contrib/test_vars.sh index b81461161..ef4753dd1 100644 --- a/primitives/contrib/test_vars.sh +++ b/primitives/contrib/test_vars.sh @@ -5,10 +5,10 @@ # shellcheck disable=SC2034 # Test these features with "std" enabled. -FEATURES_WITH_STD="ordered serde" +FEATURES_WITH_STD="ordered serde arbitrary" # Test these features without "std" enabled. -FEATURES_WITHOUT_STD="alloc ordered serde" +FEATURES_WITHOUT_STD="alloc ordered serde arbitrary" # Run these examples. EXAMPLES="" diff --git a/primitives/src/locktime/absolute.rs b/primitives/src/locktime/absolute.rs index 93f31a2c6..4c7632988 100644 --- a/primitives/src/locktime/absolute.rs +++ b/primitives/src/locktime/absolute.rs @@ -8,6 +8,8 @@ use core::cmp::Ordering; use core::fmt; +#[cfg(feature = "arbitrary")] +use arbitrary::{Arbitrary, Unstructured}; #[cfg(all(test, mutate))] use mutagen::mutate; use units::parse::{self, PrefixedHexError, UnprefixedHexError}; @@ -395,6 +397,14 @@ impl ordered::ArbitraryOrd for LockTime { } } +#[cfg(feature = "arbitrary")] +impl<'a> Arbitrary<'a> for LockTime { + fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result { + let l = u32::arbitrary(u)?; + Ok(LockTime::from_consensus(l)) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/primitives/src/sequence.rs b/primitives/src/sequence.rs index d3ee0013d..3246753d7 100644 --- a/primitives/src/sequence.rs +++ b/primitives/src/sequence.rs @@ -24,6 +24,8 @@ use serde::{Deserialize, Serialize}; use units::locktime::relative::TimeOverflowError; #[cfg(feature = "alloc")] use units::parse::{self, PrefixedHexError, UnprefixedHexError}; +#[cfg(feature = "arbitrary")] +use arbitrary::{Arbitrary, Unstructured}; #[cfg(feature = "alloc")] use crate::locktime::relative; @@ -238,3 +240,11 @@ impl fmt::Debug for Sequence { #[cfg(feature = "alloc")] units::impl_parse_str_from_int_infallible!(Sequence, u32, from_consensus); + +#[cfg(feature = "arbitrary")] +impl<'a> Arbitrary<'a> for Sequence { + fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result { + let s = u32::arbitrary(u)?; + Ok(Sequence(s)) + } +} diff --git a/primitives/src/transaction.rs b/primitives/src/transaction.rs index 6d705e724..a47f680bd 100644 --- a/primitives/src/transaction.rs +++ b/primitives/src/transaction.rs @@ -13,6 +13,8 @@ use core::fmt; use hashes::sha256d; +#[cfg(feature = "arbitrary")] +use arbitrary::{Arbitrary, Unstructured}; hashes::hash_newtype! { /// A bitcoin transaction hash/transaction ID. @@ -70,3 +72,20 @@ impl Version { impl fmt::Display for Version { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Display::fmt(&self.0, f) } } + +#[cfg(feature = "arbitrary")] +impl<'a> Arbitrary<'a> for Version { + fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result { + let v = i32::arbitrary(u)?; + Ok(Version(v)) + } +} + +#[cfg(feature = "arbitrary")] +impl<'a> Arbitrary<'a> for Txid { + fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result { + let arbitrary_bytes = u.arbitrary()?; + let t = sha256d::Hash::from_byte_array(arbitrary_bytes); + Ok(Txid(t)) + } +}