From f659a7aca323caa7d134d2af2243d1a5caac9e52 Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Tue, 13 Sep 2022 10:43:43 +1000 Subject: [PATCH 1/8] base58: Remove key related errors The key related errors are incorrect because they are circular, we have a base58 error variant in `key::Error` and two key error variants in `base58::Error`. Remove the key errors from the `base58::Error` type. --- bitcoin/src/util/base58.rs | 27 +-------------------------- 1 file changed, 1 insertion(+), 26 deletions(-) diff --git a/bitcoin/src/util/base58.rs b/bitcoin/src/util/base58.rs index 1f878a78..2a796162 100644 --- a/bitcoin/src/util/base58.rs +++ b/bitcoin/src/util/base58.rs @@ -12,11 +12,7 @@ use crate::prelude::*; use core::{fmt, str, iter, slice}; use core::convert::TryInto; -use bitcoin_internals::write_err; -use crate::hashes::{sha256d, Hash, hex}; -use secp256k1; - -use crate::util::key; +use crate::hashes::{sha256d, Hash}; /// An error that might occur during base58 decoding #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)] @@ -36,11 +32,6 @@ pub enum Error { InvalidAddressVersion(u8), /// Checked data was less than 4 bytes TooShort(usize), - /// Secp256k1 error while parsing a secret key - Secp256k1(secp256k1::Error), - /// Hex decoding error - // TODO: Remove this as part of crate-smashing, there should not be any key related errors in this module - Hex(hex::Error) } impl fmt::Display for Error { @@ -52,8 +43,6 @@ impl fmt::Display for Error { Error::InvalidExtendedKeyVersion(ref v) => write!(f, "extended key version {:#04x?} is invalid for this base58 type", v), Error::InvalidAddressVersion(ref v) => write!(f, "address version {} is invalid for this base58 type", v), Error::TooShort(_) => write!(f, "base58ck data not even long enough for a checksum"), - Error::Secp256k1(ref e) => write_err!(f, "secp256k1 error while parsing secret key"; e), - Error::Hex(ref e) => write_err!(f, "hexadecimal decoding error"; e) } } } @@ -71,8 +60,6 @@ impl std::error::Error for Error { | InvalidExtendedKeyVersion(_) | InvalidAddressVersion(_) | TooShort(_) => None, - Secp256k1(e) => Some(e), - Hex(e) => Some(e), } } } @@ -265,18 +252,6 @@ pub fn check_encode_slice_to_fmt(fmt: &mut fmt::Formatter, data: &[u8]) -> fmt:: format_iter(fmt, iter) } -#[doc(hidden)] -impl From for Error { - fn from(e: key::Error) -> Self { - match e { - key::Error::Secp256k1(e) => Error::Secp256k1(e), - key::Error::Base58(e) => e, - key::Error::InvalidKeyPrefix(_) => Error::Secp256k1(secp256k1::Error::InvalidPublicKey), - key::Error::Hex(e) => Error::Hex(e) - } - } -} - #[cfg(test)] mod tests { use super::*; From 27f2cba6230e0b2e4b2eda045d0f5375c65a2842 Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Tue, 13 Sep 2022 10:53:40 +1000 Subject: [PATCH 2/8] base58: Use alternate form to print hex Currently we are manually adding `0x` in calls to `write!`, this is unnecessary since the alternate form already adds the `0x`. Was verified with ``` #[test] fn bad_checksum_error_hex_format() { let want = "invalid base58 character 0xab"; let got = format!("{}", Error::BadByte(0xAB)); assert_eq!(got, want) } ``` Use alternate form to print hex. --- bitcoin/src/util/base58.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bitcoin/src/util/base58.rs b/bitcoin/src/util/base58.rs index 2a796162..f69a5275 100644 --- a/bitcoin/src/util/base58.rs +++ b/bitcoin/src/util/base58.rs @@ -37,8 +37,8 @@ pub enum Error { impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { - Error::BadByte(b) => write!(f, "invalid base58 character 0x{:x}", b), - Error::BadChecksum(exp, actual) => write!(f, "base58ck checksum 0x{:x} does not match expected 0x{:x}", actual, exp), + Error::BadByte(b) => write!(f, "invalid base58 character {:#x}", b), + Error::BadChecksum(exp, actual) => write!(f, "base58ck checksum {:#x} does not match expected {:#x}", actual, exp), Error::InvalidLength(ell) => write!(f, "length {} invalid for this base58 type", ell), Error::InvalidExtendedKeyVersion(ref v) => write!(f, "extended key version {:#04x?} is invalid for this base58 type", v), Error::InvalidAddressVersion(ref v) => write!(f, "address version {} is invalid for this base58 type", v), From a43234e7ab3aa9364e6ed1321f99942281663a27 Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Tue, 13 Sep 2022 10:56:18 +1000 Subject: [PATCH 3/8] base58: Make SmallVec methods private The `SmallVec` type is private, it does not need public methods. --- bitcoin/src/util/base58.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/bitcoin/src/util/base58.rs b/bitcoin/src/util/base58.rs index f69a5275..890f7208 100644 --- a/bitcoin/src/util/base58.rs +++ b/bitcoin/src/util/base58.rs @@ -73,7 +73,7 @@ struct SmallVec { } impl SmallVec { - pub fn new() -> SmallVec { + fn new() -> SmallVec { SmallVec { len: 0, stack: [T::default(); 100], @@ -81,7 +81,7 @@ impl SmallVec { } } - pub fn push(&mut self, val: T) { + fn push(&mut self, val: T) { if self.len < 100 { self.stack[self.len] = val; self.len += 1; @@ -90,12 +90,12 @@ impl SmallVec { } } - pub fn iter(&self) -> iter::Chain, slice::Iter> { + fn iter(&self) -> iter::Chain, slice::Iter> { // If len<100 then we just append an empty vec self.stack[0..self.len].iter().chain(self.heap.iter()) } - pub fn iter_mut(&mut self) -> iter::Chain, slice::IterMut> { + fn iter_mut(&mut self) -> iter::Chain, slice::IterMut> { // If len<100 then we just append an empty vec self.stack[0..self.len].iter_mut().chain(self.heap.iter_mut()) } From d362e6286a3207a3c0c46175c1ffbce912dc3443 Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Tue, 13 Sep 2022 11:03:28 +1000 Subject: [PATCH 4/8] base58: Improve rustdocs Improve the rustdocs by doing: - Use full sentences - Use typical project line length - Use third person tense for functions --- bitcoin/src/util/base58.rs | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/bitcoin/src/util/base58.rs b/bitcoin/src/util/base58.rs index 890f7208..22206273 100644 --- a/bitcoin/src/util/base58.rs +++ b/bitcoin/src/util/base58.rs @@ -14,23 +14,22 @@ use core::convert::TryInto; use crate::hashes::{sha256d, Hash}; -/// An error that might occur during base58 decoding +/// An error that might occur during base58 decoding. #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)] #[non_exhaustive] pub enum Error { - /// Invalid character encountered + /// Invalid character encountered. BadByte(u8), - /// Checksum was not correct (expected, actual) + /// Checksum was not correct (expected, actual). BadChecksum(u32, u32), - /// The length (in bytes) of the object was not correct - /// Note that if the length is excessively long the provided length may be - /// an estimate (and the checksum step may be skipped). + /// The length (in bytes) of the object was not correct, note that if the length is excessively + /// long the provided length may be an estimate (and the checksum step may be skipped). InvalidLength(usize), - /// Extended Key version byte(s) were not recognized + /// Extended Key version byte(s) were not recognized. InvalidExtendedKeyVersion([u8; 4]), - /// Address version byte were not recognized + /// Address version byte were not recognized. InvalidAddressVersion(u8), - /// Checked data was less than 4 bytes + /// Checked data was less than 4 bytes. TooShort(usize), } @@ -122,7 +121,7 @@ static BASE58_DIGITS: [Option; 128] = [ Some(55), Some(56), Some(57), None, None, None, None, None, // 120-127 ]; -/// Decode base58-encoded string into a byte vector +/// Decodes a base58-encoded string into a byte vector. pub fn from(data: &str) -> Result, Error> { // 11/15 is just over log_256(58) let mut scratch = vec![0u8; 1 + data.len() * 11 / 15]; @@ -153,7 +152,7 @@ pub fn from(data: &str) -> Result, Error> { Ok(ret) } -/// Decode a base58check-encoded string +/// Decodes a base58check-encoded string into a byte vector verifying the checksum. pub fn from_check(data: &str) -> Result, Error> { let mut ret: Vec = from(data)?; if ret.len() < 4 { @@ -226,13 +225,14 @@ where } -/// Directly encode a slice as base58 +/// Encodes `data` as a base58 string. pub fn encode_slice(data: &[u8]) -> String { encode_iter(data.iter().cloned()) } -/// Obtain a string with the base58check encoding of a slice -/// (Tack the first 4 256-digits of the object's Bitcoin hash onto the end.) +/// Encodes `data` as a base58 string including the checksum. +/// +/// The checksum is the first 4 256-digits of the object's Bitcoin hash, concatenated onto the end. pub fn check_encode_slice(data: &[u8]) -> String { let checksum = sha256d::Hash::hash(data); encode_iter( @@ -242,8 +242,9 @@ pub fn check_encode_slice(data: &[u8]) -> String { ) } -/// Obtain a string with the base58check encoding of a slice -/// (Tack the first 4 256-digits of the object's Bitcoin hash onto the end.) +/// Encodes `data` as base58, including the checksum, into a formatter. +/// +/// The checksum is the first 4 256-digits of the object's Bitcoin hash, concatenated onto the end. pub fn check_encode_slice_to_fmt(fmt: &mut fmt::Formatter, data: &[u8]) -> fmt::Result { let checksum = sha256d::Hash::hash(data); let iter = data.iter() From a94af5c05204b137bb9e91dbd2c01d2d44397f5f Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Tue, 13 Sep 2022 11:13:16 +1000 Subject: [PATCH 5/8] base58: Re-order code Code is arguably easier to read if the most important stuff comes first. In the old days, when writing C, we had to put definitions before they were used but in Rust this is not the case Re-order the `base58` file so that the public API functions are up the top then other helper functions are defined _after_ they are called. Refactor only, no logic changes. --- bitcoin/src/util/base58.rs | 229 ++++++++++++++++++------------------- 1 file changed, 114 insertions(+), 115 deletions(-) diff --git a/bitcoin/src/util/base58.rs b/bitcoin/src/util/base58.rs index 22206273..941d6edf 100644 --- a/bitcoin/src/util/base58.rs +++ b/bitcoin/src/util/base58.rs @@ -14,92 +14,6 @@ use core::convert::TryInto; use crate::hashes::{sha256d, Hash}; -/// An error that might occur during base58 decoding. -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)] -#[non_exhaustive] -pub enum Error { - /// Invalid character encountered. - BadByte(u8), - /// Checksum was not correct (expected, actual). - BadChecksum(u32, u32), - /// The length (in bytes) of the object was not correct, note that if the length is excessively - /// long the provided length may be an estimate (and the checksum step may be skipped). - InvalidLength(usize), - /// Extended Key version byte(s) were not recognized. - InvalidExtendedKeyVersion([u8; 4]), - /// Address version byte were not recognized. - InvalidAddressVersion(u8), - /// Checked data was less than 4 bytes. - TooShort(usize), -} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - Error::BadByte(b) => write!(f, "invalid base58 character {:#x}", b), - Error::BadChecksum(exp, actual) => write!(f, "base58ck checksum {:#x} does not match expected {:#x}", actual, exp), - Error::InvalidLength(ell) => write!(f, "length {} invalid for this base58 type", ell), - Error::InvalidExtendedKeyVersion(ref v) => write!(f, "extended key version {:#04x?} is invalid for this base58 type", v), - Error::InvalidAddressVersion(ref v) => write!(f, "address version {} is invalid for this base58 type", v), - Error::TooShort(_) => write!(f, "base58ck data not even long enough for a checksum"), - } - } -} - -#[cfg(feature = "std")] -#[cfg_attr(docsrs, doc(cfg(feature = "std")))] -impl std::error::Error for Error { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - use self::Error::*; - - match self { - BadByte(_) - | BadChecksum(_, _) - | InvalidLength(_) - | InvalidExtendedKeyVersion(_) - | InvalidAddressVersion(_) - | TooShort(_) => None, - } - } -} - -/// Vector-like object that holds the first 100 elements on the stack. If more space is needed it -/// will be allocated on the heap. -struct SmallVec { - len: usize, - stack: [T; 100], - heap: Vec, -} - -impl SmallVec { - fn new() -> SmallVec { - SmallVec { - len: 0, - stack: [T::default(); 100], - heap: Vec::new(), - } - } - - fn push(&mut self, val: T) { - if self.len < 100 { - self.stack[self.len] = val; - self.len += 1; - } else { - self.heap.push(val); - } - } - - fn iter(&self) -> iter::Chain, slice::Iter> { - // If len<100 then we just append an empty vec - self.stack[0..self.len].iter().chain(self.heap.iter()) - } - - fn iter_mut(&mut self) -> iter::Chain, slice::IterMut> { - // If len<100 then we just append an empty vec - self.stack[0..self.len].iter_mut().chain(self.heap.iter_mut()) - } -} - static BASE58_CHARS: &[u8] = b"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; static BASE58_DIGITS: [Option; 128] = [ @@ -174,6 +88,43 @@ pub fn from_check(data: &str) -> Result, Error> { Ok(ret) } +/// Encodes `data` as a base58 string. +pub fn encode_slice(data: &[u8]) -> String { + encode_iter(data.iter().cloned()) +} + +/// Encodes `data` as a base58 string including the checksum. +/// +/// The checksum is the first 4 256-digits of the object's Bitcoin hash, concatenated onto the end. +pub fn check_encode_slice(data: &[u8]) -> String { + let checksum = sha256d::Hash::hash(data); + encode_iter( + data.iter() + .cloned() + .chain(checksum[0..4].iter().cloned()) + ) +} + +/// Encodes `data` as base58, including the checksum, into a formatter. +/// +/// The checksum is the first 4 256-digits of the object's Bitcoin hash, concatenated onto the end. +pub fn check_encode_slice_to_fmt(fmt: &mut fmt::Formatter, data: &[u8]) -> fmt::Result { + let checksum = sha256d::Hash::hash(data); + let iter = data.iter() + .cloned() + .chain(checksum[0..4].iter().cloned()); + format_iter(fmt, iter) +} + +fn encode_iter(data: I) -> String +where + I: Iterator + Clone, +{ + let mut ret = String::new(); + format_iter(&mut ret, data).expect("writing into string shouldn't fail"); + ret +} + fn format_iter(writer: &mut W, data: I) -> Result<(), fmt::Error> where I: Iterator + Clone, @@ -215,42 +166,90 @@ where Ok(()) } -fn encode_iter(data: I) -> String -where - I: Iterator + Clone, -{ - let mut ret = String::new(); - format_iter(&mut ret, data).expect("writing into string shouldn't fail"); - ret +/// Vector-like object that holds the first 100 elements on the stack. If more space is needed it +/// will be allocated on the heap. +struct SmallVec { + len: usize, + stack: [T; 100], + heap: Vec, } +impl SmallVec { + fn new() -> SmallVec { + SmallVec { + len: 0, + stack: [T::default(); 100], + heap: Vec::new(), + } + } -/// Encodes `data` as a base58 string. -pub fn encode_slice(data: &[u8]) -> String { - encode_iter(data.iter().cloned()) + fn push(&mut self, val: T) { + if self.len < 100 { + self.stack[self.len] = val; + self.len += 1; + } else { + self.heap.push(val); + } + } + + fn iter(&self) -> iter::Chain, slice::Iter> { + // If len<100 then we just append an empty vec + self.stack[0..self.len].iter().chain(self.heap.iter()) + } + + fn iter_mut(&mut self) -> iter::Chain, slice::IterMut> { + // If len<100 then we just append an empty vec + self.stack[0..self.len].iter_mut().chain(self.heap.iter_mut()) + } } -/// Encodes `data` as a base58 string including the checksum. -/// -/// The checksum is the first 4 256-digits of the object's Bitcoin hash, concatenated onto the end. -pub fn check_encode_slice(data: &[u8]) -> String { - let checksum = sha256d::Hash::hash(data); - encode_iter( - data.iter() - .cloned() - .chain(checksum[0..4].iter().cloned()) - ) +/// An error that might occur during base58 decoding. +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)] +#[non_exhaustive] +pub enum Error { + /// Invalid character encountered. + BadByte(u8), + /// Checksum was not correct (expected, actual). + BadChecksum(u32, u32), + /// The length (in bytes) of the object was not correct, note that if the length is excessively + /// long the provided length may be an estimate (and the checksum step may be skipped). + InvalidLength(usize), + /// Extended Key version byte(s) were not recognized. + InvalidExtendedKeyVersion([u8; 4]), + /// Address version byte were not recognized. + InvalidAddressVersion(u8), + /// Checked data was less than 4 bytes. + TooShort(usize), } -/// Encodes `data` as base58, including the checksum, into a formatter. -/// -/// The checksum is the first 4 256-digits of the object's Bitcoin hash, concatenated onto the end. -pub fn check_encode_slice_to_fmt(fmt: &mut fmt::Formatter, data: &[u8]) -> fmt::Result { - let checksum = sha256d::Hash::hash(data); - let iter = data.iter() - .cloned() - .chain(checksum[0..4].iter().cloned()); - format_iter(fmt, iter) +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + Error::BadByte(b) => write!(f, "invalid base58 character {:#x}", b), + Error::BadChecksum(exp, actual) => write!(f, "base58ck checksum {:#x} does not match expected {:#x}", actual, exp), + Error::InvalidLength(ell) => write!(f, "length {} invalid for this base58 type", ell), + Error::InvalidExtendedKeyVersion(ref v) => write!(f, "extended key version {:#04x?} is invalid for this base58 type", v), + Error::InvalidAddressVersion(ref v) => write!(f, "address version {} is invalid for this base58 type", v), + Error::TooShort(_) => write!(f, "base58ck data not even long enough for a checksum"), + } + } +} + +#[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +impl std::error::Error for Error { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + use self::Error::*; + + match self { + BadByte(_) + | BadChecksum(_, _) + | InvalidLength(_) + | InvalidExtendedKeyVersion(_) + | InvalidAddressVersion(_) + | TooShort(_) => None, + } + } } #[cfg(test)] From 6151d4c8413a68081799fcdc4d425ae5d2bbf26d Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Tue, 13 Sep 2022 11:40:49 +1000 Subject: [PATCH 6/8] base58: Rename public functions The `base58` module is for encoding and decoding, it makes sense for the public functions to be called `encode` and `decode`. We also have some functions that operate on data with a checksum, for these it makes sense to tack `check` onto the _end_ of the function name. With this applied the public API is: - decode - decode_check - encode - encode_check - encode_check_to_fmt --- bitcoin/src/address.rs | 6 +-- bitcoin/src/bip32.rs | 8 ++-- bitcoin/src/util/base58.rs | 78 +++++++++++++++++++++++++++----------- bitcoin/src/util/key.rs | 6 +-- 4 files changed, 66 insertions(+), 32 deletions(-) diff --git a/bitcoin/src/address.rs b/bitcoin/src/address.rs index 6a5e3e3d..dee8ddd6 100644 --- a/bitcoin/src/address.rs +++ b/bitcoin/src/address.rs @@ -523,13 +523,13 @@ impl<'a> fmt::Display for AddressEncoding<'a> { let mut prefixed = [0; 21]; prefixed[0] = self.p2pkh_prefix; prefixed[1..].copy_from_slice(&hash[..]); - base58::check_encode_slice_to_fmt(fmt, &prefixed[..]) + base58::encode_check_to_fmt(fmt, &prefixed[..]) } Payload::ScriptHash(hash) => { let mut prefixed = [0; 21]; prefixed[0] = self.p2sh_prefix; prefixed[1..].copy_from_slice(&hash[..]); - base58::check_encode_slice_to_fmt(fmt, &prefixed[..]) + base58::encode_check_to_fmt(fmt, &prefixed[..]) } Payload::WitnessProgram { version, program: prog } => { let mut upper_writer; @@ -839,7 +839,7 @@ impl FromStr for Address { if s.len() > 50 { return Err(Error::Base58(base58::Error::InvalidLength(s.len() * 11 / 15))); } - let data = base58::from_check(s)?; + let data = base58::decode_check(s)?; if data.len() != 21 { return Err(Error::Base58(base58::Error::InvalidLength(data.len()))); } diff --git a/bitcoin/src/bip32.rs b/bitcoin/src/bip32.rs index dcb9dbc7..51f60de3 100644 --- a/bitcoin/src/bip32.rs +++ b/bitcoin/src/bip32.rs @@ -780,7 +780,7 @@ impl ExtendedPubKey { impl fmt::Display for ExtendedPrivKey { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { - base58::check_encode_slice_to_fmt(fmt, &self.encode()[..]) + base58::encode_check_to_fmt(fmt, &self.encode()[..]) } } @@ -788,7 +788,7 @@ impl FromStr for ExtendedPrivKey { type Err = Error; fn from_str(inp: &str) -> Result { - let data = base58::from_check(inp)?; + let data = base58::decode_check(inp)?; if data.len() != 78 { return Err(base58::Error::InvalidLength(data.len()).into()); @@ -800,7 +800,7 @@ impl FromStr for ExtendedPrivKey { impl fmt::Display for ExtendedPubKey { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { - base58::check_encode_slice_to_fmt(fmt, &self.encode()[..]) + base58::encode_check_to_fmt(fmt, &self.encode()[..]) } } @@ -808,7 +808,7 @@ impl FromStr for ExtendedPubKey { type Err = Error; fn from_str(inp: &str) -> Result { - let data = base58::from_check(inp)?; + let data = base58::decode_check(inp)?; if data.len() != 78 { return Err(base58::Error::InvalidLength(data.len()).into()); diff --git a/bitcoin/src/util/base58.rs b/bitcoin/src/util/base58.rs index 941d6edf..84aca244 100644 --- a/bitcoin/src/util/base58.rs +++ b/bitcoin/src/util/base58.rs @@ -36,7 +36,13 @@ static BASE58_DIGITS: [Option; 128] = [ ]; /// Decodes a base58-encoded string into a byte vector. +#[deprecated(since = "0.30.0", note = "Use base58::decode() instead")] pub fn from(data: &str) -> Result, Error> { + decode(data) +} + +/// Decodes a base58-encoded string into a byte vector. +pub fn decode(data: &str) -> Result, Error> { // 11/15 is just over log_256(58) let mut scratch = vec![0u8; 1 + data.len() * 11 / 15]; // Build in base 256 @@ -67,8 +73,14 @@ pub fn from(data: &str) -> Result, Error> { } /// Decodes a base58check-encoded string into a byte vector verifying the checksum. +#[deprecated(since = "0.30.0", note = "Use base58::decode_check() instead")] pub fn from_check(data: &str) -> Result, Error> { - let mut ret: Vec = from(data)?; + decode_check(data) +} + +/// Decodes a base58check-encoded string into a byte vector verifying the checksum. +pub fn decode_check(data: &str) -> Result, Error> { + let mut ret: Vec = decode(data)?; if ret.len() < 4 { return Err(Error::TooShort(ret.len())); } @@ -89,14 +101,28 @@ pub fn from_check(data: &str) -> Result, Error> { } /// Encodes `data` as a base58 string. +#[deprecated(since = "0.30.0", note = "Use base58::encode() instead")] pub fn encode_slice(data: &[u8]) -> String { + encode(data) +} + +/// Encodes `data` as a base58 string (see also `base58::encode_check()`). +pub fn encode(data: &[u8]) -> String { encode_iter(data.iter().cloned()) } /// Encodes `data` as a base58 string including the checksum. /// /// The checksum is the first 4 256-digits of the object's Bitcoin hash, concatenated onto the end. +#[deprecated(since = "0.30.0", note = "Use base58::encode_check() instead")] pub fn check_encode_slice(data: &[u8]) -> String { + encode_check(data) +} + +/// Encodes `data` as a base58 string including the checksum. +/// +/// The checksum is the first 4 256-digits of the object's Bitcoin hash, concatenated onto the end. +pub fn encode_check(data: &[u8]) -> String { let checksum = sha256d::Hash::hash(data); encode_iter( data.iter() @@ -108,7 +134,15 @@ pub fn check_encode_slice(data: &[u8]) -> String { /// Encodes `data` as base58, including the checksum, into a formatter. /// /// The checksum is the first 4 256-digits of the object's Bitcoin hash, concatenated onto the end. +#[deprecated(since = "0.30.0", note = "Use base58::encode_check_to_fmt() instead")] pub fn check_encode_slice_to_fmt(fmt: &mut fmt::Formatter, data: &[u8]) -> fmt::Result { + encode_check_to_fmt(fmt, data) +} + +/// Encodes a slice as base58, including the checksum, into a formatter. +/// +/// The checksum is the first 4 256-digits of the object's Bitcoin hash, concatenated onto the end. +pub fn encode_check_to_fmt(fmt: &mut fmt::Formatter, data: &[u8]) -> fmt::Result { let checksum = sha256d::Hash::hash(data); let iter = data.iter() .cloned() @@ -260,17 +294,17 @@ mod tests { #[test] fn test_base58_encode() { // Basics - assert_eq!(&encode_slice(&[0][..]), "1"); - assert_eq!(&encode_slice(&[1][..]), "2"); - assert_eq!(&encode_slice(&[58][..]), "21"); - assert_eq!(&encode_slice(&[13, 36][..]), "211"); + assert_eq!(&encode(&[0][..]), "1"); + assert_eq!(&encode(&[1][..]), "2"); + assert_eq!(&encode(&[58][..]), "21"); + assert_eq!(&encode(&[13, 36][..]), "211"); // Leading zeroes - assert_eq!(&encode_slice(&[0, 13, 36][..]), "1211"); - assert_eq!(&encode_slice(&[0, 0, 0, 0, 13, 36][..]), "1111211"); + assert_eq!(&encode(&[0, 13, 36][..]), "1211"); + assert_eq!(&encode(&[0, 0, 0, 0, 13, 36][..]), "1111211"); // Long input (>100 bytes => has to use heap) - let res = encode_slice("BitcoinBitcoinBitcoinBitcoinBitcoinBitcoinBitcoinBitcoinBitcoinBit\ + let res = encode("BitcoinBitcoinBitcoinBitcoinBitcoinBitcoinBitcoinBitcoinBitcoinBit\ coinBitcoinBitcoinBitcoinBitcoinBitcoinBitcoinBitcoinBitcoinBitcoinBitcoin".as_bytes()); let exp = "ZqC5ZdfpZRi7fjA8hbhX5pEE96MdH9hEaC1YouxscPtbJF16qVWksHWR4wwvx7MotFcs2ChbJqK8KJ9X\ wZznwWn1JFDhhTmGo9v6GjAVikzCsBWZehu7bm22xL8b5zBR5AsBygYRwbFJsNwNkjpyFuDKwmsUTKvkULCvucPJrN5\ @@ -279,39 +313,39 @@ mod tests { // Addresses let addr = Vec::from_hex("00f8917303bfa8ef24f292e8fa1419b20460ba064d").unwrap(); - assert_eq!(&check_encode_slice(&addr[..]), "1PfJpZsjreyVrqeoAfabrRwwjQyoSQMmHH"); + assert_eq!(&encode_check(&addr[..]), "1PfJpZsjreyVrqeoAfabrRwwjQyoSQMmHH"); } #[test] fn test_base58_decode() { // Basics - assert_eq!(from("1").ok(), Some(vec![0u8])); - assert_eq!(from("2").ok(), Some(vec![1u8])); - assert_eq!(from("21").ok(), Some(vec![58u8])); - assert_eq!(from("211").ok(), Some(vec![13u8, 36])); + assert_eq!(decode("1").ok(), Some(vec![0u8])); + assert_eq!(decode("2").ok(), Some(vec![1u8])); + assert_eq!(decode("21").ok(), Some(vec![58u8])); + assert_eq!(decode("211").ok(), Some(vec![13u8, 36])); // Leading zeroes - assert_eq!(from("1211").ok(), Some(vec![0u8, 13, 36])); - assert_eq!(from("111211").ok(), Some(vec![0u8, 0, 0, 13, 36])); + assert_eq!(decode("1211").ok(), Some(vec![0u8, 13, 36])); + assert_eq!(decode("111211").ok(), Some(vec![0u8, 0, 0, 13, 36])); // Addresses - assert_eq!(from_check("1PfJpZsjreyVrqeoAfabrRwwjQyoSQMmHH").ok(), + assert_eq!(decode_check("1PfJpZsjreyVrqeoAfabrRwwjQyoSQMmHH").ok(), Some(Vec::from_hex("00f8917303bfa8ef24f292e8fa1419b20460ba064d").unwrap())); // Non Base58 char. - assert_eq!(from("¢").unwrap_err(), Error::BadByte(194)); + assert_eq!(decode("¢").unwrap_err(), Error::BadByte(194)); } #[test] fn test_base58_roundtrip() { let s = "xprv9wTYmMFdV23N2TdNG573QoEsfRrWKQgWeibmLntzniatZvR9BmLnvSxqu53Kw1UmYPxLgboyZQaXwTCg8MSY3H2EU4pWcQDnRnrVA1xe8fs"; - let v: Vec = from_check(s).unwrap(); - assert_eq!(check_encode_slice(&v[..]), s); - assert_eq!(from_check(&check_encode_slice(&v[..])).ok(), Some(v)); + let v: Vec = decode_check(s).unwrap(); + assert_eq!(encode_check(&v[..]), s); + assert_eq!(decode_check(&encode_check(&v[..])).ok(), Some(v)); // Check that empty slice passes roundtrip. - assert_eq!(from_check(&check_encode_slice(&[])), Ok(vec![])); + assert_eq!(decode_check(&encode_check(&[])), Ok(vec![])); // Check that `len > 4` is enforced. - assert_eq!(from_check(&encode_slice(&[1,2,3])), Err(Error::TooShort(3))); + assert_eq!(decode_check(&encode(&[1,2,3])), Err(Error::TooShort(3))); } } diff --git a/bitcoin/src/util/key.rs b/bitcoin/src/util/key.rs index c53d648f..b8f06ff1 100644 --- a/bitcoin/src/util/key.rs +++ b/bitcoin/src/util/key.rs @@ -360,9 +360,9 @@ impl PrivateKey { ret[1..33].copy_from_slice(&self.inner[..]); let privkey = if self.compressed { ret[33] = 1; - base58::check_encode_slice(&ret[..]) + base58::encode_check(&ret[..]) } else { - base58::check_encode_slice(&ret[..33]) + base58::encode_check(&ret[..33]) }; fmt.write_str(&privkey) } @@ -377,7 +377,7 @@ impl PrivateKey { /// Parse WIF encoded private key. pub fn from_wif(wif: &str) -> Result { - let data = base58::from_check(wif)?; + let data = base58::decode_check(wif)?; let compressed = match data.len() { 33 => false, From 0f01cb9f51d0cc62a02533736dcd28c0bf58f194 Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Thu, 29 Sep 2022 05:10:19 +1000 Subject: [PATCH 7/8] Use rustdoc summary Rustdoc comment is too long, use a summary and a longer section as is convention. --- bitcoin/src/util/base58.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/bitcoin/src/util/base58.rs b/bitcoin/src/util/base58.rs index 84aca244..6569ca2a 100644 --- a/bitcoin/src/util/base58.rs +++ b/bitcoin/src/util/base58.rs @@ -245,8 +245,10 @@ pub enum Error { BadByte(u8), /// Checksum was not correct (expected, actual). BadChecksum(u32, u32), - /// The length (in bytes) of the object was not correct, note that if the length is excessively - /// long the provided length may be an estimate (and the checksum step may be skipped). + /// The length (in bytes) of the object was not correct. + /// + /// Note that if the length is excessively long the provided length may be an estimate (and the + /// checksum step may be skipped). InvalidLength(usize), /// Extended Key version byte(s) were not recognized. InvalidExtendedKeyVersion([u8; 4]), From 4e9ff972ad8f31b1ed3c2a9962ebfba8edd93e4e Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Thu, 29 Sep 2022 05:13:12 +1000 Subject: [PATCH 8/8] Improve checksum documentation Improve the wording describing the base58 checksum. --- bitcoin/src/util/base58.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/bitcoin/src/util/base58.rs b/bitcoin/src/util/base58.rs index 6569ca2a..e6c86e7b 100644 --- a/bitcoin/src/util/base58.rs +++ b/bitcoin/src/util/base58.rs @@ -113,7 +113,7 @@ pub fn encode(data: &[u8]) -> String { /// Encodes `data` as a base58 string including the checksum. /// -/// The checksum is the first 4 256-digits of the object's Bitcoin hash, concatenated onto the end. +/// The checksum is the first four bytes of the sha256d of the data, concatenated onto the end. #[deprecated(since = "0.30.0", note = "Use base58::encode_check() instead")] pub fn check_encode_slice(data: &[u8]) -> String { encode_check(data) @@ -121,7 +121,7 @@ pub fn check_encode_slice(data: &[u8]) -> String { /// Encodes `data` as a base58 string including the checksum. /// -/// The checksum is the first 4 256-digits of the object's Bitcoin hash, concatenated onto the end. +/// The checksum is the first four bytes of the sha256d of the data, concatenated onto the end. pub fn encode_check(data: &[u8]) -> String { let checksum = sha256d::Hash::hash(data); encode_iter( @@ -133,7 +133,7 @@ pub fn encode_check(data: &[u8]) -> String { /// Encodes `data` as base58, including the checksum, into a formatter. /// -/// The checksum is the first 4 256-digits of the object's Bitcoin hash, concatenated onto the end. +/// The checksum is the first four bytes of the sha256d of the data, concatenated onto the end. #[deprecated(since = "0.30.0", note = "Use base58::encode_check_to_fmt() instead")] pub fn check_encode_slice_to_fmt(fmt: &mut fmt::Formatter, data: &[u8]) -> fmt::Result { encode_check_to_fmt(fmt, data) @@ -141,7 +141,7 @@ pub fn check_encode_slice_to_fmt(fmt: &mut fmt::Formatter, data: &[u8]) -> fmt:: /// Encodes a slice as base58, including the checksum, into a formatter. /// -/// The checksum is the first 4 256-digits of the object's Bitcoin hash, concatenated onto the end. +/// The checksum is the first four bytes of the sha256d of the data, concatenated onto the end. pub fn encode_check_to_fmt(fmt: &mut fmt::Formatter, data: &[u8]) -> fmt::Result { let checksum = sha256d::Hash::hash(data); let iter = data.iter()