From 374c6118dc02e462db025af5ac0f7836ec260ccf Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Fri, 30 Aug 2024 03:48:31 +1000 Subject: [PATCH 1/6] Deprecate Script::fmt_asm and to_asm_str The `Script::fmt_asm` function is a legacy from days yore before `Display` printed asm. We no longer need it. Deprecate `Script::fmt_asm` and use the private `bytes_to_asm_fmt` or `Display` impls. --- bitcoin/src/blockdata/script/borrowed.rs | 4 +++- bitcoin/src/blockdata/script/builder.rs | 4 ++-- bitcoin/src/blockdata/script/mod.rs | 4 ++-- bitcoin/src/blockdata/script/tests.rs | 22 +++++++++++----------- 4 files changed, 18 insertions(+), 16 deletions(-) diff --git a/bitcoin/src/blockdata/script/borrowed.rs b/bitcoin/src/blockdata/script/borrowed.rs index c0c4da3e6..749915014 100644 --- a/bitcoin/src/blockdata/script/borrowed.rs +++ b/bitcoin/src/blockdata/script/borrowed.rs @@ -481,14 +481,16 @@ crate::internal_macros::define_extension_trait! { } /// Writes the human-readable assembly representation of the script to the formatter. + #[deprecated(since = "TBD", note = "use the script's Display impl instead")] fn fmt_asm(&self, f: &mut dyn fmt::Write) -> fmt::Result { bytes_to_asm_fmt(self.as_ref(), f) } /// Returns the human-readable assembly representation of the script. + #[deprecated(since = "TBD", note = "use `to_string()` instead")] fn to_asm_string(&self) -> String { let mut buf = String::new(); - self.fmt_asm(&mut buf).unwrap(); + bytes_to_asm_fmt(self.as_ref(), &mut buf).expect("in-memory writers don't fail"); buf } diff --git a/bitcoin/src/blockdata/script/builder.rs b/bitcoin/src/blockdata/script/builder.rs index 5303d37ef..6ed1456bc 100644 --- a/bitcoin/src/blockdata/script/builder.rs +++ b/bitcoin/src/blockdata/script/builder.rs @@ -7,7 +7,7 @@ use crate::locktime::absolute; use crate::opcodes::all::*; use crate::opcodes::{self, Opcode}; use crate::prelude::Vec; -use crate::script::{ScriptBufExt as _, ScriptExt as _, ScriptExtPriv as _}; +use crate::script::{ScriptBufExt as _, ScriptExtPriv as _}; use crate::Sequence; /// An Object which can be used to construct a script piece by piece. @@ -126,7 +126,7 @@ impl From> for Builder { } impl fmt::Display for Builder { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.0.fmt_asm(f) } + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&self.0, f) } } internals::debug_from_display!(Builder); diff --git a/bitcoin/src/blockdata/script/mod.rs b/bitcoin/src/blockdata/script/mod.rs index aee687b28..4b2530598 100644 --- a/bitcoin/src/blockdata/script/mod.rs +++ b/bitcoin/src/blockdata/script/mod.rs @@ -415,7 +415,7 @@ impl AsMut<[u8]> for ScriptBuf { impl fmt::Debug for Script { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_str("Script(")?; - self.fmt_asm(f)?; + bytes_to_asm_fmt(self.as_ref(), f)?; f.write_str(")") } } @@ -426,7 +426,7 @@ impl fmt::Debug for ScriptBuf { impl fmt::Display for Script { #[inline] - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.fmt_asm(f) } + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { bytes_to_asm_fmt(self.as_ref(), f) } } impl fmt::Display for ScriptBuf { diff --git a/bitcoin/src/blockdata/script/tests.rs b/bitcoin/src/blockdata/script/tests.rs index d2c53f450..7fdc91553 100644 --- a/bitcoin/src/blockdata/script/tests.rs +++ b/bitcoin/src/blockdata/script/tests.rs @@ -483,40 +483,40 @@ fn script_json_serialize() { #[test] fn script_asm() { assert_eq!( - ScriptBuf::from_hex("6363636363686868686800").unwrap().to_asm_string(), + ScriptBuf::from_hex("6363636363686868686800").unwrap().to_string(), "OP_IF OP_IF OP_IF OP_IF OP_IF OP_ENDIF OP_ENDIF OP_ENDIF OP_ENDIF OP_ENDIF OP_0" ); assert_eq!( - ScriptBuf::from_hex("6363636363686868686800").unwrap().to_asm_string(), + ScriptBuf::from_hex("6363636363686868686800").unwrap().to_string(), "OP_IF OP_IF OP_IF OP_IF OP_IF OP_ENDIF OP_ENDIF OP_ENDIF OP_ENDIF OP_ENDIF OP_0" ); - assert_eq!(ScriptBuf::from_hex("2102715e91d37d239dea832f1460e91e368115d8ca6cc23a7da966795abad9e3b699ac").unwrap().to_asm_string(), + assert_eq!(ScriptBuf::from_hex("2102715e91d37d239dea832f1460e91e368115d8ca6cc23a7da966795abad9e3b699ac").unwrap().to_string(), "OP_PUSHBYTES_33 02715e91d37d239dea832f1460e91e368115d8ca6cc23a7da966795abad9e3b699 OP_CHECKSIG"); // Elements Alpha peg-out transaction with some signatures removed for brevity. Mainly to test PUSHDATA1 - assert_eq!(ScriptBuf::from_hex("0047304402202457e78cc1b7f50d0543863c27de75d07982bde8359b9e3316adec0aec165f2f02200203fd331c4e4a4a02f48cf1c291e2c0d6b2f7078a784b5b3649fca41f8794d401004cf1552103244e602b46755f24327142a0517288cebd159eccb6ccf41ea6edf1f601e9af952103bbbacc302d19d29dbfa62d23f37944ae19853cf260c745c2bea739c95328fcb721039227e83246bd51140fe93538b2301c9048be82ef2fb3c7fc5d78426ed6f609ad210229bf310c379b90033e2ecb07f77ecf9b8d59acb623ab7be25a0caed539e2e6472103703e2ed676936f10b3ce9149fa2d4a32060fb86fa9a70a4efe3f21d7ab90611921031e9b7c6022400a6bb0424bbcde14cff6c016b91ee3803926f3440abf5c146d05210334667f975f55a8455d515a2ef1c94fdfa3315f12319a14515d2a13d82831f62f57ae").unwrap().to_asm_string(), + assert_eq!(ScriptBuf::from_hex("0047304402202457e78cc1b7f50d0543863c27de75d07982bde8359b9e3316adec0aec165f2f02200203fd331c4e4a4a02f48cf1c291e2c0d6b2f7078a784b5b3649fca41f8794d401004cf1552103244e602b46755f24327142a0517288cebd159eccb6ccf41ea6edf1f601e9af952103bbbacc302d19d29dbfa62d23f37944ae19853cf260c745c2bea739c95328fcb721039227e83246bd51140fe93538b2301c9048be82ef2fb3c7fc5d78426ed6f609ad210229bf310c379b90033e2ecb07f77ecf9b8d59acb623ab7be25a0caed539e2e6472103703e2ed676936f10b3ce9149fa2d4a32060fb86fa9a70a4efe3f21d7ab90611921031e9b7c6022400a6bb0424bbcde14cff6c016b91ee3803926f3440abf5c146d05210334667f975f55a8455d515a2ef1c94fdfa3315f12319a14515d2a13d82831f62f57ae").unwrap().to_string(), "OP_0 OP_PUSHBYTES_71 304402202457e78cc1b7f50d0543863c27de75d07982bde8359b9e3316adec0aec165f2f02200203fd331c4e4a4a02f48cf1c291e2c0d6b2f7078a784b5b3649fca41f8794d401 OP_0 OP_PUSHDATA1 552103244e602b46755f24327142a0517288cebd159eccb6ccf41ea6edf1f601e9af952103bbbacc302d19d29dbfa62d23f37944ae19853cf260c745c2bea739c95328fcb721039227e83246bd51140fe93538b2301c9048be82ef2fb3c7fc5d78426ed6f609ad210229bf310c379b90033e2ecb07f77ecf9b8d59acb623ab7be25a0caed539e2e6472103703e2ed676936f10b3ce9149fa2d4a32060fb86fa9a70a4efe3f21d7ab90611921031e9b7c6022400a6bb0424bbcde14cff6c016b91ee3803926f3440abf5c146d05210334667f975f55a8455d515a2ef1c94fdfa3315f12319a14515d2a13d82831f62f57ae"); // Various weird scripts found in transaction 6d7ed9914625c73c0288694a6819196a27ef6c08f98e1270d975a8e65a3dc09a // which triggerred overflow bugs on 32-bit machines in script formatting in the past. assert_eq!( - ScriptBuf::from_hex("01").unwrap().to_asm_string(), + ScriptBuf::from_hex("01").unwrap().to_string(), "OP_PUSHBYTES_1 " ); assert_eq!( - ScriptBuf::from_hex("0201").unwrap().to_asm_string(), + ScriptBuf::from_hex("0201").unwrap().to_string(), "OP_PUSHBYTES_2 " ); - assert_eq!(ScriptBuf::from_hex("4c").unwrap().to_asm_string(), ""); + assert_eq!(ScriptBuf::from_hex("4c").unwrap().to_string(), ""); assert_eq!( - ScriptBuf::from_hex("4c0201").unwrap().to_asm_string(), + ScriptBuf::from_hex("4c0201").unwrap().to_string(), "OP_PUSHDATA1 " ); - assert_eq!(ScriptBuf::from_hex("4d").unwrap().to_asm_string(), ""); + assert_eq!(ScriptBuf::from_hex("4d").unwrap().to_string(), ""); assert_eq!( - ScriptBuf::from_hex("4dffff01").unwrap().to_asm_string(), + ScriptBuf::from_hex("4dffff01").unwrap().to_string(), "OP_PUSHDATA2 " ); assert_eq!( - ScriptBuf::from_hex("4effffffff01").unwrap().to_asm_string(), + ScriptBuf::from_hex("4effffffff01").unwrap().to_string(), "OP_PUSHDATA4 " ); } From b0675a4a4f5b5a60bfa72bce990e91aef70500cd Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Wed, 21 Aug 2024 11:11:43 +1000 Subject: [PATCH 2/6] Use Script::len instead of inner field In preparation for moving the `Script` type to `primitives` stop accessing the inner field to get the length, call `len` directly. --- bitcoin/src/blockdata/script/borrowed.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/bitcoin/src/blockdata/script/borrowed.rs b/bitcoin/src/blockdata/script/borrowed.rs index 749915014..c5219d047 100644 --- a/bitcoin/src/blockdata/script/borrowed.rs +++ b/bitcoin/src/blockdata/script/borrowed.rs @@ -175,7 +175,7 @@ crate::internal_macros::define_extension_trait! { /// > byte vector pushed is called the "witness program". #[inline] fn witness_version(&self) -> Option { - let script_len = self.0.len(); + let script_len = self.len(); if !(4..=42).contains(&script_len) { return None; } @@ -197,7 +197,7 @@ crate::internal_macros::define_extension_trait! { /// Checks whether a script pubkey is a P2SH output. #[inline] fn is_p2sh(&self) -> bool { - self.0.len() == 23 + self.len() == 23 && self.0[0] == OP_HASH160.to_u8() && self.0[1] == OP_PUSHBYTES_20.to_u8() && self.0[22] == OP_EQUAL.to_u8() @@ -206,7 +206,7 @@ crate::internal_macros::define_extension_trait! { /// Checks whether a script pubkey is a P2PKH output. #[inline] fn is_p2pkh(&self) -> bool { - self.0.len() == 25 + self.len() == 25 && self.0[0] == OP_DUP.to_u8() && self.0[1] == OP_HASH160.to_u8() && self.0[2] == OP_PUSHBYTES_20.to_u8() @@ -293,7 +293,7 @@ crate::internal_macros::define_extension_trait! { /// Checks whether a script pubkey is a P2WSH output. #[inline] fn is_p2wsh(&self) -> bool { - self.0.len() == 34 + self.len() == 34 && self.witness_version() == Some(WitnessVersion::V0) && self.0[1] == OP_PUSHBYTES_32.to_u8() } @@ -301,7 +301,7 @@ crate::internal_macros::define_extension_trait! { /// Checks whether a script pubkey is a P2WPKH output. #[inline] fn is_p2wpkh(&self) -> bool { - self.0.len() == 22 + self.len() == 22 && self.witness_version() == Some(WitnessVersion::V0) && self.0[1] == OP_PUSHBYTES_20.to_u8() } @@ -309,7 +309,7 @@ crate::internal_macros::define_extension_trait! { /// Checks whether a script pubkey is a P2TR output. #[inline] fn is_p2tr(&self) -> bool { - self.0.len() == 34 + self.len() == 34 && self.witness_version() == Some(WitnessVersion::V1) && self.0[1] == OP_PUSHBYTES_32.to_u8() } @@ -331,7 +331,7 @@ crate::internal_macros::define_extension_trait! { /// What this function considers to be standard may change without warning pending Bitcoin Core /// changes. #[inline] - fn is_standard_op_return(&self) -> bool { self.is_op_return() && self.0.len() <= 80 } + fn is_standard_op_return(&self) -> bool { self.is_op_return() && self.len() <= 80 } /// Checks whether a script is trivially known to have no satisfying input. /// From 8b82363d97b6d1d69fe216843bc6ad1c52e275c2 Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Wed, 21 Aug 2024 11:14:28 +1000 Subject: [PATCH 3/6] Use Script::as_bytes instead of inner when indexing In preparation for moving the `Script` type to `primitives` stop accessing the inner field before doing slice operations, use `as_bytes` to first get at the slice. --- bitcoin/src/blockdata/script/borrowed.rs | 34 ++++++++++++------------ 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/bitcoin/src/blockdata/script/borrowed.rs b/bitcoin/src/blockdata/script/borrowed.rs index c5219d047..fdecffc85 100644 --- a/bitcoin/src/blockdata/script/borrowed.rs +++ b/bitcoin/src/blockdata/script/borrowed.rs @@ -180,8 +180,8 @@ crate::internal_macros::define_extension_trait! { return None; } - let ver_opcode = Opcode::from(self.0[0]); // Version 0 or PUSHNUM_1-PUSHNUM_16 - let push_opbyte = self.0[1]; // Second byte push opcode 2-40 bytes + let ver_opcode = Opcode::from(self.as_bytes()[0]); // Version 0 or PUSHNUM_1-PUSHNUM_16 + let push_opbyte = self.as_bytes()[1]; // Second byte push opcode 2-40 bytes if push_opbyte < OP_PUSHBYTES_2.to_u8() || push_opbyte > OP_PUSHBYTES_40.to_u8() { return None; @@ -198,20 +198,20 @@ crate::internal_macros::define_extension_trait! { #[inline] fn is_p2sh(&self) -> bool { self.len() == 23 - && self.0[0] == OP_HASH160.to_u8() - && self.0[1] == OP_PUSHBYTES_20.to_u8() - && self.0[22] == OP_EQUAL.to_u8() + && self.as_bytes()[0] == OP_HASH160.to_u8() + && self.as_bytes()[1] == OP_PUSHBYTES_20.to_u8() + && self.as_bytes()[22] == OP_EQUAL.to_u8() } /// Checks whether a script pubkey is a P2PKH output. #[inline] fn is_p2pkh(&self) -> bool { self.len() == 25 - && self.0[0] == OP_DUP.to_u8() - && self.0[1] == OP_HASH160.to_u8() - && self.0[2] == OP_PUSHBYTES_20.to_u8() - && self.0[23] == OP_EQUALVERIFY.to_u8() - && self.0[24] == OP_CHECKSIG.to_u8() + && self.as_bytes()[0] == OP_DUP.to_u8() + && self.as_bytes()[1] == OP_HASH160.to_u8() + && self.as_bytes()[2] == OP_PUSHBYTES_20.to_u8() + && self.as_bytes()[23] == OP_EQUALVERIFY.to_u8() + && self.as_bytes()[24] == OP_CHECKSIG.to_u8() } /// Checks whether a script is push only. @@ -295,7 +295,7 @@ crate::internal_macros::define_extension_trait! { fn is_p2wsh(&self) -> bool { self.len() == 34 && self.witness_version() == Some(WitnessVersion::V0) - && self.0[1] == OP_PUSHBYTES_32.to_u8() + && self.as_bytes()[1] == OP_PUSHBYTES_32.to_u8() } /// Checks whether a script pubkey is a P2WPKH output. @@ -303,7 +303,7 @@ crate::internal_macros::define_extension_trait! { fn is_p2wpkh(&self) -> bool { self.len() == 22 && self.witness_version() == Some(WitnessVersion::V0) - && self.0[1] == OP_PUSHBYTES_20.to_u8() + && self.as_bytes()[1] == OP_PUSHBYTES_20.to_u8() } /// Checks whether a script pubkey is a P2TR output. @@ -311,7 +311,7 @@ crate::internal_macros::define_extension_trait! { fn is_p2tr(&self) -> bool { self.len() == 34 && self.witness_version() == Some(WitnessVersion::V1) - && self.0[1] == OP_PUSHBYTES_32.to_u8() + && self.as_bytes()[1] == OP_PUSHBYTES_32.to_u8() } /// Check if this is a consensus-valid OP_RETURN output. @@ -320,7 +320,7 @@ crate::internal_macros::define_extension_trait! { /// [`is_standard_op_return()`](Self::is_standard_op_return) instead. #[inline] fn is_op_return(&self) -> bool { - match self.0.first() { + match self.as_bytes().first() { Some(b) => *b == OP_RETURN.to_u8(), None => false, } @@ -345,7 +345,7 @@ crate::internal_macros::define_extension_trait! { fn is_provably_unspendable(&self) -> bool { use crate::opcodes::Class::{IllegalOp, ReturnOp}; - match self.0.first() { + match self.as_bytes().first() { Some(b) => { let first = Opcode::from(*b); let class = first.classify(opcodes::ClassifyContext::Legacy); @@ -447,7 +447,7 @@ crate::internal_macros::define_extension_trait! { /// To force minimal pushes, use [`instructions_minimal`](Self::instructions_minimal). #[inline] fn instructions(&self) -> Instructions { - Instructions { data: self.0.iter(), enforce_minimal: false } + Instructions { data: self.as_bytes().iter(), enforce_minimal: false } } /// Iterates over the script instructions while enforcing minimal pushes. @@ -456,7 +456,7 @@ crate::internal_macros::define_extension_trait! { /// is not minimal. #[inline] fn instructions_minimal(&self) -> Instructions { - Instructions { data: self.0.iter(), enforce_minimal: true } + Instructions { data: self.as_bytes().iter(), enforce_minimal: true } } /// Iterates over the script instructions and their indices. From 900af453ff3cfb789aca7e1e3fc5689d40bb5c51 Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Wed, 21 Aug 2024 11:20:40 +1000 Subject: [PATCH 4/6] Stop accessing inner ScriptBuf field when encoding In preparation for moving the `ScriptBuf` type to `primitives` stop accessing the inner field when encoding/decoding, use `as_script` and `from_bytes` instead. --- bitcoin/src/blockdata/script/mod.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/bitcoin/src/blockdata/script/mod.rs b/bitcoin/src/blockdata/script/mod.rs index 4b2530598..c0df71c0b 100644 --- a/bitcoin/src/blockdata/script/mod.rs +++ b/bitcoin/src/blockdata/script/mod.rs @@ -616,14 +616,14 @@ impl<'de> serde::Deserialize<'de> for ScriptBuf { impl Encodable for Script { #[inline] fn consensus_encode(&self, w: &mut W) -> Result { - crate::consensus::encode::consensus_encode_with_size(&self.0, w) + crate::consensus::encode::consensus_encode_with_size(self.as_bytes(), w) } } impl Encodable for ScriptBuf { #[inline] fn consensus_encode(&self, w: &mut W) -> Result { - self.0.consensus_encode(w) + self.as_script().consensus_encode(w) } } @@ -632,7 +632,8 @@ impl Decodable for ScriptBuf { fn consensus_decode_from_finite_reader( r: &mut R, ) -> Result { - Ok(ScriptBuf(Decodable::consensus_decode_from_finite_reader(r)?)) + let v: Vec = Decodable::consensus_decode_from_finite_reader(r)?; + Ok(ScriptBuf::from_bytes(v)) } } From 95f2a8dab61aa238e3a2244095f01a66b888fba8 Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Wed, 21 Aug 2024 11:33:17 +1000 Subject: [PATCH 5/6] Do not access ScriptBuf inner from builder The `Builder` is staying in `bitcoin` while the `ScriptBuf` is moving to `primitives`, so we cannot access the inner field of `ScriptBuf`. Use the new `as_byte_vec` hack to mutate the inner `ScriptBuf` field. --- bitcoin/src/blockdata/script/builder.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bitcoin/src/blockdata/script/builder.rs b/bitcoin/src/blockdata/script/builder.rs index 6ed1456bc..c8465df9f 100644 --- a/bitcoin/src/blockdata/script/builder.rs +++ b/bitcoin/src/blockdata/script/builder.rs @@ -7,7 +7,7 @@ use crate::locktime::absolute; use crate::opcodes::all::*; use crate::opcodes::{self, Opcode}; use crate::prelude::Vec; -use crate::script::{ScriptBufExt as _, ScriptExtPriv as _}; +use crate::script::{ScriptBufExt as _, ScriptBufExtPriv as _, ScriptExtPriv as _}; use crate::Sequence; /// An Object which can be used to construct a script piece by piece. @@ -82,7 +82,7 @@ impl Builder { // "duplicated code" because we need to update `1` field match opcode_to_verify(self.1) { Some(opcode) => { - (self.0).0.pop(); + (self.0).as_byte_vec().pop(); self.push_opcode(opcode) } None => self.push_opcode(OP_VERIFY), From 8f2f4cbb3cf172907367f78f440fd5ebb32d422c Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Tue, 27 Aug 2024 10:12:18 +1000 Subject: [PATCH 6/6] Re-order optional dependencies The optional dependencies are ordered and separated by whitspace in a manner that may not be obvious (or even have a reason). Some of this is because since use of `?` deps changed name. Put all the optional deps together in alphabetic order. --- bitcoin/Cargo.toml | 5 ++--- primitives/Cargo.toml | 1 - 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/bitcoin/Cargo.toml b/bitcoin/Cargo.toml index e2e748f06..d73832d57 100644 --- a/bitcoin/Cargo.toml +++ b/bitcoin/Cargo.toml @@ -35,13 +35,12 @@ primitives = { package = "bitcoin-primitives", version = "0.100.0", default-feat secp256k1 = { version = "0.29.0", default-features = false, features = ["hashes", "alloc"] } units = { package = "bitcoin-units", version = "0.1.0", default-features = false, features = ["alloc"] } +arbitrary = { version = "1", optional = true } base64 = { version = "0.22.0", optional = true } -ordered = { version = "0.2.0", optional = true } # `bitcoinconsensus` version includes metadata which indicates the version of Core. Use `cargo tree` to see it. bitcoinconsensus = { version = "0.106.0", default-features = false, optional = true } - +ordered = { version = "0.2.0", optional = true } serde = { version = "1.0.103", default-features = false, features = [ "derive", "alloc" ], optional = true } -arbitrary = { version = "1", optional = true } [dev-dependencies] internals = { package = "bitcoin-internals", version = "0.3.0", features = ["test-serde"] } diff --git a/primitives/Cargo.toml b/primitives/Cargo.toml index 9a293fdaf..32710bf83 100644 --- a/primitives/Cargo.toml +++ b/primitives/Cargo.toml @@ -26,7 +26,6 @@ io = { package = "bitcoin-io", version = "0.1.1", default-features = false } units = { package = "bitcoin-units", version = "0.1.0", default-features = false } ordered = { version = "0.2.0", optional = true } - serde = { version = "1.0.103", default-features = false, features = ["derive"], optional = true } [dev-dependencies]