Merge rust-bitcoin/rust-bitcoin#2497: Add the `FromScriptError` for handling errors in `address`

c2d658ac05 Add `P2shError` for handling errors related to P2sh (harshit933)
5182a8d7a8 Remove unused variants from `Address::Error` (harshit933)
05b24946eb Add the `FromScriptError` for handling errors in `address` (harshit933)

Pull request description:

  This commit adds the `FromScriptError` struct to handle the errors while generating address from any script. It includes :
  - Unrecognized script error.
  - Witness Program error.
  - Witness Version error.

ACKs for top commit:
  tcharding:
    ACK c2d658ac05
  apoelstra:
    ACK c2d658ac05

Tree-SHA512: 891eed787129aaf1b664cc16d325178d5d2f77cc41a0543a3d9d1a5af1b58188daece1f6a653bdc6b76b82db0490a39e9bba7fc090e3727d15ee9b8977733698
This commit is contained in:
Andrew Poelstra 2024-02-24 15:49:06 +00:00
commit d85817b880
No known key found for this signature in database
GPG Key ID: C588D63CE41B97C1
2 changed files with 82 additions and 29 deletions

View File

@ -13,14 +13,6 @@ use crate::{base58, Network};
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive] #[non_exhaustive]
pub enum Error { pub enum Error {
/// A witness version construction error.
WitnessVersion(witness_version::TryFromError),
/// A witness program error.
WitnessProgram(witness_program::Error),
/// Address size more than 520 bytes is not allowed.
ExcessiveScriptSize,
/// Script is not a p2pkh, p2sh or witness program.
UnrecognizedScript,
/// Address's network differs from required one. /// Address's network differs from required one.
NetworkValidation { NetworkValidation {
/// Network that was required. /// Network that was required.
@ -37,10 +29,6 @@ impl fmt::Display for Error {
use Error::*; use Error::*;
match *self { match *self {
WitnessVersion(ref e) => write_err!(f, "witness version construction error"; e),
WitnessProgram(ref e) => write_err!(f, "witness program error"; e),
ExcessiveScriptSize => write!(f, "script size exceed 520 bytes"),
UnrecognizedScript => write!(f, "script is not a p2pkh, p2sh or witness program"),
NetworkValidation { required, ref address } => { NetworkValidation { required, ref address } => {
write!(f, "address ")?; write!(f, "address ")?;
fmt::Display::fmt(&address.0, f)?; fmt::Display::fmt(&address.0, f)?;
@ -56,21 +44,85 @@ impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
use Error::*; use Error::*;
match self { match *self {
WitnessVersion(e) => Some(e), UnknownHrp(ref e) => Some(e),
WitnessProgram(e) => Some(e), NetworkValidation { .. } => None,
UnknownHrp(e) => Some(e),
ExcessiveScriptSize | UnrecognizedScript | NetworkValidation { .. } => None,
} }
} }
} }
impl From<witness_version::TryFromError> for Error { /// Error while generating address from script.
fn from(e: witness_version::TryFromError) -> Error { Error::WitnessVersion(e) } #[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum FromScriptError {
/// Script is not a p2pkh, p2sh or witness program.
UnrecognizedScript,
/// A witness program error.
WitnessProgram(witness_program::Error),
/// A witness version construction error.
WitnessVersion(witness_version::TryFromError),
} }
impl From<witness_program::Error> for Error { impl fmt::Display for FromScriptError {
fn from(e: witness_program::Error) -> Error { Error::WitnessProgram(e) } fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use FromScriptError::*;
match *self {
WitnessVersion(ref e) => write_err!(f, "witness version construction error"; e),
WitnessProgram(ref e) => write_err!(f, "witness program error"; e),
UnrecognizedScript => write!(f, "script is not a p2pkh, p2sh or witness program"),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for FromScriptError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
use FromScriptError::*;
match *self {
UnrecognizedScript => None,
WitnessVersion(ref e) => Some(e),
WitnessProgram(ref e) => Some(e),
}
}
}
impl From<witness_program::Error> for FromScriptError {
fn from(e : witness_program::Error) -> Self { Self::WitnessProgram(e) }
}
impl From<witness_version::TryFromError> for FromScriptError {
fn from(e: witness_version::TryFromError) -> Self { Self::WitnessVersion(e) }
}
/// Error while generating address from a p2sh script.
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum P2shError {
/// Address size more than 520 bytes is not allowed.
ExcessiveScriptSize,
}
impl fmt::Display for P2shError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use P2shError::*;
match *self {
ExcessiveScriptSize => write!(f, "script size exceed 520 bytes"),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for P2shError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
use P2shError::*;
match self {
ExcessiveScriptSize => None,
}
}
} }
/// Address type is either invalid or not supported in rust-bitcoin. /// Address type is either invalid or not supported in rust-bitcoin.

View File

@ -51,10 +51,11 @@ use crate::network::{Network, NetworkKind};
use crate::prelude::*; use crate::prelude::*;
use crate::taproot::TapNodeHash; use crate::taproot::TapNodeHash;
use self::error::P2shError;
#[rustfmt::skip] // Keep public re-exports separate. #[rustfmt::skip] // Keep public re-exports separate.
#[doc(inline)] #[doc(inline)]
pub use self::{ pub use self::{
error::{Error, ParseError, UnknownAddressTypeError, UnknownHrpError}, error::{Error, ParseError, UnknownAddressTypeError, UnknownHrpError, FromScriptError},
}; };
/// The different types of addresses. /// The different types of addresses.
@ -373,9 +374,9 @@ impl Address {
/// This address type was introduced with BIP16 and is the popular type to implement multi-sig /// This address type was introduced with BIP16 and is the popular type to implement multi-sig
/// these days. /// these days.
#[inline] #[inline]
pub fn p2sh(script: &Script, network: impl Into<NetworkKind>) -> Result<Address, Error> { pub fn p2sh(script: &Script, network: impl Into<NetworkKind>) -> Result<Address, P2shError> {
if script.len() > MAX_SCRIPT_ELEMENT_SIZE { if script.len() > MAX_SCRIPT_ELEMENT_SIZE {
return Err(Error::ExcessiveScriptSize); return Err(P2shError::ExcessiveScriptSize);
} }
let hash = script.script_hash(); let hash = script.script_hash();
Ok(Address::p2sh_from_hash(hash, network)) Ok(Address::p2sh_from_hash(hash, network))
@ -503,7 +504,7 @@ impl Address {
pub fn is_spend_standard(&self) -> bool { self.address_type().is_some() } pub fn is_spend_standard(&self) -> bool { self.address_type().is_some() }
/// Constructs an [`Address`] from an output script (`scriptPubkey`). /// Constructs an [`Address`] from an output script (`scriptPubkey`).
pub fn from_script(script: &Script, network: Network) -> Result<Address, Error> { pub fn from_script(script: &Script, network: Network) -> Result<Address, FromScriptError> {
if script.is_p2pkh() { if script.is_p2pkh() {
let bytes = script.as_bytes()[3..23].try_into().expect("statically 20B long"); let bytes = script.as_bytes()[3..23].try_into().expect("statically 20B long");
let hash = PubkeyHash::from_byte_array(bytes); let hash = PubkeyHash::from_byte_array(bytes);
@ -519,7 +520,7 @@ impl Address {
let program = WitnessProgram::new(version, &script.as_bytes()[2..])?; let program = WitnessProgram::new(version, &script.as_bytes()[2..])?;
Ok(Address::from_witness_program(program, network)) Ok(Address::from_witness_program(program, network))
} else { } else {
Err(Error::UnrecognizedScript) Err(FromScriptError::UnrecognizedScript)
} }
} }
@ -861,7 +862,7 @@ mod tests {
#[test] #[test]
fn test_p2sh_parse_for_large_script() { fn test_p2sh_parse_for_large_script() {
let script = ScriptBuf::from_hex("552103a765fc35b3f210b95223846b36ef62a4e53e34e2925270c2c7906b92c9f718eb2103c327511374246759ec8d0b89fa6c6b23b33e11f92c5bc155409d86de0c79180121038cae7406af1f12f4786d820a1466eec7bc5785a1b5e4a387eca6d797753ef6db2103252bfb9dcaab0cd00353f2ac328954d791270203d66c2be8b430f115f451b8a12103e79412d42372c55dd336f2eb6eb639ef9d74a22041ba79382c74da2338fe58ad21035049459a4ebc00e876a9eef02e72a3e70202d3d1f591fc0dd542f93f642021f82102016f682920d9723c61b27f562eb530c926c00106004798b6471e8c52c60ee02057ae12123122313123123ac1231231231231313123131231231231313212313213123123552103a765fc35b3f210b95223846b36ef62a4e53e34e2925270c2c7906b92c9f718eb2103c327511374246759ec8d0b89fa6c6b23b33e11f92c5bc155409d86de0c79180121038cae7406af1f12f4786d820a1466eec7bc5785a1b5e4a387eca6d797753ef6db2103252bfb9dcaab0cd00353f2ac328954d791270203d66c2be8b430f115f451b8a12103e79412d42372c55dd336f2eb6eb639ef9d74a22041ba79382c74da2338fe58ad21035049459a4ebc00e876a9eef02e72a3e70202d3d1f591fc0dd542f93f642021f82102016f682920d9723c61b27f562eb530c926c00106004798b6471e8c52c60ee02057ae12123122313123123ac1231231231231313123131231231231313212313213123123552103a765fc35b3f210b95223846b36ef62a4e53e34e2925270c2c7906b92c9f718eb2103c327511374246759ec8d0b89fa6c6b23b33e11f92c5bc155409d86de0c79180121038cae7406af1f12f4786d820a1466eec7bc5785a1b5e4a387eca6d797753ef6db2103252bfb9dcaab0cd00353f2ac328954d791270203d66c2be8b430f115f451b8a12103e79412d42372c55dd336f2eb6eb639ef9d74a22041ba79382c74da2338fe58ad21035049459a4ebc00e876a9eef02e72a3e70202d3d1f591fc0dd542f93f642021f82102016f682920d9723c61b27f562eb530c926c00106004798b6471e8c52c60ee02057ae12123122313123123ac1231231231231313123131231231231313212313213123123").unwrap(); let script = ScriptBuf::from_hex("552103a765fc35b3f210b95223846b36ef62a4e53e34e2925270c2c7906b92c9f718eb2103c327511374246759ec8d0b89fa6c6b23b33e11f92c5bc155409d86de0c79180121038cae7406af1f12f4786d820a1466eec7bc5785a1b5e4a387eca6d797753ef6db2103252bfb9dcaab0cd00353f2ac328954d791270203d66c2be8b430f115f451b8a12103e79412d42372c55dd336f2eb6eb639ef9d74a22041ba79382c74da2338fe58ad21035049459a4ebc00e876a9eef02e72a3e70202d3d1f591fc0dd542f93f642021f82102016f682920d9723c61b27f562eb530c926c00106004798b6471e8c52c60ee02057ae12123122313123123ac1231231231231313123131231231231313212313213123123552103a765fc35b3f210b95223846b36ef62a4e53e34e2925270c2c7906b92c9f718eb2103c327511374246759ec8d0b89fa6c6b23b33e11f92c5bc155409d86de0c79180121038cae7406af1f12f4786d820a1466eec7bc5785a1b5e4a387eca6d797753ef6db2103252bfb9dcaab0cd00353f2ac328954d791270203d66c2be8b430f115f451b8a12103e79412d42372c55dd336f2eb6eb639ef9d74a22041ba79382c74da2338fe58ad21035049459a4ebc00e876a9eef02e72a3e70202d3d1f591fc0dd542f93f642021f82102016f682920d9723c61b27f562eb530c926c00106004798b6471e8c52c60ee02057ae12123122313123123ac1231231231231313123131231231231313212313213123123552103a765fc35b3f210b95223846b36ef62a4e53e34e2925270c2c7906b92c9f718eb2103c327511374246759ec8d0b89fa6c6b23b33e11f92c5bc155409d86de0c79180121038cae7406af1f12f4786d820a1466eec7bc5785a1b5e4a387eca6d797753ef6db2103252bfb9dcaab0cd00353f2ac328954d791270203d66c2be8b430f115f451b8a12103e79412d42372c55dd336f2eb6eb639ef9d74a22041ba79382c74da2338fe58ad21035049459a4ebc00e876a9eef02e72a3e70202d3d1f591fc0dd542f93f642021f82102016f682920d9723c61b27f562eb530c926c00106004798b6471e8c52c60ee02057ae12123122313123123ac1231231231231313123131231231231313212313213123123").unwrap();
assert_eq!(Address::p2sh(&script, NetworkKind::Test), Err(Error::ExcessiveScriptSize)); assert_eq!(Address::p2sh(&script, NetworkKind::Test), Err(P2shError::ExcessiveScriptSize));
} }
#[test] #[test]
@ -1236,13 +1237,13 @@ mod tests {
.unwrap(); .unwrap();
let invalid_segwitv0_script = let invalid_segwitv0_script =
ScriptBuf::from_hex("001161458e330389cd0437ee9fe3641d70cc18").unwrap(); ScriptBuf::from_hex("001161458e330389cd0437ee9fe3641d70cc18").unwrap();
let expected = Err(Error::UnrecognizedScript); let expected = Err(FromScriptError::UnrecognizedScript);
assert_eq!(Address::from_script(&bad_p2wpkh, Network::Bitcoin), expected); assert_eq!(Address::from_script(&bad_p2wpkh, Network::Bitcoin), expected);
assert_eq!(Address::from_script(&bad_p2wsh, Network::Bitcoin), expected); assert_eq!(Address::from_script(&bad_p2wsh, Network::Bitcoin), expected);
assert_eq!( assert_eq!(
Address::from_script(&invalid_segwitv0_script, Network::Bitcoin), Address::from_script(&invalid_segwitv0_script, Network::Bitcoin),
Err(Error::WitnessProgram(witness_program::Error::InvalidSegwitV0Length(17))) Err(FromScriptError::WitnessProgram(witness_program::Error::InvalidSegwitV0Length(17)))
); );
} }