Finalize the script hex APIs
Recently we made an attempt at making the hex APIs for scripts easier to use, better documented, and shown via an example. After that work we decided it would be better if `LowerHex`/`UpperHex` did not have the prefix. We also wanted to further clarify the inherent function names to make the all explicit. See GitHub issue #4316 for the thread of discussion. Note that this PR does not require changes to the serde regression test which were non changed in the original work either.
This commit is contained in:
parent
41f26cf090
commit
6b90e42e78
|
@ -15,10 +15,16 @@ use bitcoin::ScriptBuf;
|
|||
fn main() {
|
||||
let pk = "b472a266d0bd89c13706a4132ccfb16f7c3b9fcb".parse::<WPubkeyHash>().unwrap();
|
||||
|
||||
// TL;DR Use `to_hex_string` and `from_hex`.
|
||||
// TL;DR Use `to_hex_string_prefixed` and `from_hex_prefixed`.
|
||||
let script_code = script::p2wpkh_script_code(pk);
|
||||
let hex = script_code.to_hex_string();
|
||||
let decoded = ScriptBuf::from_hex(&hex).unwrap();
|
||||
let hex = script_code.to_hex_string_prefixed();
|
||||
let decoded = ScriptBuf::from_hex_prefixed(&hex).unwrap();
|
||||
assert_eq!(decoded, script_code);
|
||||
|
||||
// Or if you prefer: `to_hex_string_no_length_prefix` and `from_hex_no_length_prefix`.
|
||||
let script_code = script::p2wpkh_script_code(pk);
|
||||
let hex = script_code.to_hex_string_no_length_prefix();
|
||||
let decoded = ScriptBuf::from_hex_no_length_prefix(&hex).unwrap();
|
||||
assert_eq!(decoded, script_code);
|
||||
|
||||
// Writes the script as human-readable eg, OP_DUP OP_HASH160 OP_PUSHBYTES_20 ...
|
||||
|
@ -27,28 +33,25 @@ fn main() {
|
|||
// We do not implement parsing scripts from human-readable format.
|
||||
// let decoded = s.parse::<ScriptBuf>().unwrap();
|
||||
|
||||
// This is equivalent to consensus encoding i.e., includes the length prefix.
|
||||
// This is not equivalent to consensus encoding i.e., does not include the length prefix.
|
||||
let hex_lower_hex_trait = format!("{script_code:x}");
|
||||
println!("hex created using `LowerHex`: {hex_lower_hex_trait}");
|
||||
|
||||
// The `deserialize_hex` function requires the length prefix.
|
||||
assert_eq!(encode::deserialize_hex::<ScriptBuf>(&hex_lower_hex_trait).unwrap(), script_code);
|
||||
// And so does `from_hex`.
|
||||
assert_eq!(ScriptBuf::from_hex(&hex_lower_hex_trait).unwrap(), script_code);
|
||||
|
||||
// And we also provide an explicit constructor that does not use the length prefix.
|
||||
let other = ScriptBuf::from_hex_no_length_prefix(&hex_lower_hex_trait).unwrap();
|
||||
// Without a prefix the script parses but its not the one we meant.
|
||||
assert_ne!(other, script_code);
|
||||
assert!(encode::deserialize_hex::<ScriptBuf>(&hex_lower_hex_trait).is_err());
|
||||
// And so does `from_hex_prefixed`.
|
||||
assert!(ScriptBuf::from_hex_prefixed(&hex_lower_hex_trait).is_err());
|
||||
// But we provide an explicit constructor that does not.
|
||||
assert_eq!(ScriptBuf::from_hex_no_length_prefix(&hex_lower_hex_trait).unwrap(), script_code);
|
||||
|
||||
// This is consensus encoding i.e., includes the length prefix.
|
||||
let hex_inherent = script_code.to_hex_string(); // Defined in `ScriptExt`.
|
||||
println!("hex created using inherent `to_hex_string`: {hex_inherent}");
|
||||
let hex_inherent = script_code.to_hex_string_prefixed(); // Defined in `ScriptExt`.
|
||||
println!("hex created using inherent `to_hex_string_prefixed`: {hex_inherent}");
|
||||
|
||||
// The inverse of `to_hex_string` is `from_hex`.
|
||||
let decoded = ScriptBuf::from_hex(&hex_inherent).unwrap(); // Defined in `ScriptBufExt`.
|
||||
// The inverse of `to_hex_string_prefixed` is `from_hex_string_prefixed`.
|
||||
let decoded = ScriptBuf::from_hex_prefixed(&hex_inherent).unwrap(); // Defined in `ScriptBufExt`.
|
||||
assert_eq!(decoded, script_code);
|
||||
// We can also parse the output of `to_hex_string` using `deserialize_hex`.
|
||||
// We can also parse the output of `to_hex_string_prefixed` using `deserialize_hex`.
|
||||
let decoded = encode::deserialize_hex::<ScriptBuf>(&hex_inherent).unwrap();
|
||||
assert_eq!(decoded, script_code);
|
||||
|
||||
|
@ -58,8 +61,10 @@ fn main() {
|
|||
|
||||
let decoded: ScriptBuf = encode::deserialize_hex(&encoded).unwrap();
|
||||
assert_eq!(decoded, script_code);
|
||||
|
||||
// And we can mix these to calls because both include the length prefix.
|
||||
let decoded = ScriptBuf::from_hex(&encoded).unwrap();
|
||||
let encoded = encode::serialize_hex(&script_code);
|
||||
let decoded = ScriptBuf::from_hex_prefixed(&encoded).unwrap();
|
||||
assert_eq!(decoded, script_code);
|
||||
|
||||
// Encode/decode using a byte vector.
|
||||
|
|
|
@ -376,12 +376,19 @@ crate::internal_macros::define_extension_trait! {
|
|||
fn to_asm_string(&self) -> String { self.to_string() }
|
||||
|
||||
/// Consensus encodes the script as lower-case hex.
|
||||
fn to_hex_string(&self) -> String { consensus::encode::serialize_hex(self) }
|
||||
#[deprecated(since = "TBD", note = "use `to_hex_string_prefixed()` instead")]
|
||||
fn to_hex_string(&self) -> String { self.to_hex_string_prefixed() }
|
||||
|
||||
/// Consensus encodes the script as lower-case hex.
|
||||
fn to_hex_string_prefixed(&self) -> String { consensus::encode::serialize_hex(self) }
|
||||
|
||||
/// Consensus encodes the script as lower-case hex.
|
||||
///
|
||||
/// This is **not** consensus encoding, you likely want to use `to_hex_string`. The returned
|
||||
/// hex string will not include the length prefix.
|
||||
/// This is **not** consensus encoding, you likely want to use `to_hex_string_prefixed`.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// The returned hex string will not include the length prefix.
|
||||
fn to_hex_string_no_length_prefix(&self) -> String {
|
||||
self.as_bytes().to_lower_hex_string()
|
||||
}
|
||||
|
|
|
@ -30,10 +30,18 @@ crate::internal_macros::define_extension_trait! {
|
|||
/// Constructs a new [`ScriptBuf`] from a hex string.
|
||||
///
|
||||
/// The input string is expected to be consensus encoded i.e., includes the length prefix.
|
||||
fn from_hex(s: &str) -> Result<ScriptBuf, consensus::FromHexError> {
|
||||
fn from_hex_prefixed(s: &str) -> Result<ScriptBuf, consensus::FromHexError> {
|
||||
consensus::encode::deserialize_hex(s)
|
||||
}
|
||||
|
||||
/// Constructs a new [`ScriptBuf`] from a hex string.
|
||||
///
|
||||
/// The input string is expected to be consensus encoded i.e., includes the length prefix.
|
||||
#[deprecated(since = "TBD", note = "use `from_hex_string_prefixed()` instead")]
|
||||
fn from_hex(s: &str) -> Result<ScriptBuf, consensus::FromHexError> {
|
||||
Self::from_hex_prefixed(s)
|
||||
}
|
||||
|
||||
/// Constructs a new [`ScriptBuf`] from a hex string.
|
||||
///
|
||||
/// This is **not** consensus encoding. If your hex string is a consensus encode script then
|
||||
|
|
|
@ -431,12 +431,9 @@ impl fmt::Display for ScriptBuf {
|
|||
|
||||
#[cfg(feature = "hex")]
|
||||
impl fmt::LowerHex for Script {
|
||||
// Currently we drop all formatter options.
|
||||
#[inline]
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let compact = internals::compact_size::encode(self.as_bytes().len());
|
||||
write!(f, "{:x}", compact.as_slice().as_hex())?;
|
||||
write!(f, "{:x}", self.as_bytes().as_hex())
|
||||
fmt::LowerHex::fmt(&self.as_bytes().as_hex(), f)
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "alloc")]
|
||||
|
@ -454,12 +451,9 @@ internals::impl_to_hex_from_lower_hex!(ScriptBuf, |script_buf: &Self| script_buf
|
|||
|
||||
#[cfg(feature = "hex")]
|
||||
impl fmt::UpperHex for Script {
|
||||
// Currently we drop all formatter options.
|
||||
#[inline]
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let compact = internals::compact_size::encode(self.as_bytes().len());
|
||||
write!(f, "{:X}", compact.as_slice().as_hex())?;
|
||||
write!(f, "{:X}", self.as_bytes().as_hex())
|
||||
fmt::UpperHex::fmt(&self.as_bytes().as_hex(), f)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -511,8 +505,7 @@ impl serde::Serialize for Script {
|
|||
S: serde::Serializer,
|
||||
{
|
||||
if serializer.is_human_readable() {
|
||||
// Do not call LowerHex because we don't want to add the len prefix.
|
||||
serializer.collect_str(&format_args!("{}", self.as_bytes().as_hex()))
|
||||
serializer.collect_str(&format_args!("{:x}", self))
|
||||
} else {
|
||||
serializer.serialize_bytes(self.as_bytes())
|
||||
}
|
||||
|
@ -803,8 +796,8 @@ mod tests {
|
|||
|
||||
#[cfg(feature = "hex")]
|
||||
{
|
||||
assert_eq!(format!("{:x}", script), "0300a1b2");
|
||||
assert_eq!(format!("{:X}", script), "0300A1B2");
|
||||
assert_eq!(format!("{:x}", script), "00a1b2");
|
||||
assert_eq!(format!("{:X}", script), "00A1B2");
|
||||
}
|
||||
assert!(!format!("{:?}", script).is_empty());
|
||||
}
|
||||
|
@ -816,8 +809,8 @@ mod tests {
|
|||
|
||||
#[cfg(feature = "hex")]
|
||||
{
|
||||
assert_eq!(format!("{:x}", script_buf), "0300a1b2");
|
||||
assert_eq!(format!("{:X}", script_buf), "0300A1B2");
|
||||
assert_eq!(format!("{:x}", script_buf), "00a1b2");
|
||||
assert_eq!(format!("{:X}", script_buf), "00A1B2");
|
||||
}
|
||||
assert!(!format!("{:?}", script_buf).is_empty());
|
||||
}
|
||||
|
@ -935,7 +928,7 @@ mod tests {
|
|||
fn script_to_hex() {
|
||||
let script = Script::from_bytes(&[0xa1, 0xb2, 0xc3]);
|
||||
let hex = script.to_hex();
|
||||
assert_eq!(hex, "03a1b2c3");
|
||||
assert_eq!(hex, "a1b2c3");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -944,6 +937,6 @@ mod tests {
|
|||
fn scriptbuf_to_hex() {
|
||||
let script = ScriptBuf::from_bytes(vec![0xa1, 0xb2, 0xc3]);
|
||||
let hex = script.to_hex();
|
||||
assert_eq!(hex, "03a1b2c3");
|
||||
assert_eq!(hex, "a1b2c3");
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue