|
3 | 3 | use std::collections::BTreeMap;
|
4 | 4 | use std::fmt;
|
5 | 5 |
|
| 6 | +use bitcoin::address::FromScriptError; |
6 | 7 | use bitcoin::blockdata::script::Instruction;
|
7 | 8 | use bitcoin::psbt::Psbt;
|
8 | 9 | use bitcoin::transaction::InputWeightPrediction;
|
@@ -174,42 +175,44 @@ impl<'a> InputPair<'a> {
|
174 | 175 | }
|
175 | 176 | }
|
176 | 177 |
|
177 |
| - pub fn address_type(&self) -> AddressType { |
178 |
| - let txo = self.previous_txout().expect("PrevTxoutError"); |
| 178 | + pub fn address_type(&self) -> Result<AddressType, AddressTypeError> { |
| 179 | + let txo = self.previous_txout()?; |
179 | 180 | // HACK: Network doesn't matter for our use case of only getting the address type
|
180 | 181 | // but is required in the `from_script` interface. Hardcoded to mainnet.
|
181 |
| - Address::from_script(&txo.script_pubkey, Network::Bitcoin) |
182 |
| - .expect("Unrecognized script") |
| 182 | + Address::from_script(&txo.script_pubkey, Network::Bitcoin)? |
183 | 183 | .address_type()
|
184 |
| - .expect("UnknownAddressType") |
| 184 | + .ok_or(AddressTypeError::UnknownAddressType) |
185 | 185 | }
|
186 | 186 |
|
187 |
| - pub fn expected_input_weight(&self) -> Weight { |
| 187 | + pub fn expected_input_weight(&self) -> Result<Weight, InputWeightError> { |
188 | 188 | use bitcoin::AddressType::*;
|
189 | 189 |
|
190 | 190 | // Get the input weight prediction corresponding to spending an output of this address type
|
191 |
| - let iwp = match self.address_type() { |
192 |
| - P2pkh => InputWeightPrediction::P2PKH_COMPRESSED_MAX, |
| 191 | + let iwp = match self.address_type()? { |
| 192 | + P2pkh => Ok(InputWeightPrediction::P2PKH_COMPRESSED_MAX), |
193 | 193 | P2sh =>
|
194 | 194 | match self.psbtin.final_script_sig.as_ref().and_then(|s| redeem_script(s.as_ref()))
|
195 | 195 | {
|
196 |
| - Some(script) if script.is_witness_program() && script.is_p2wpkh() => |
| 196 | + // Nested segwit p2wpkh. |
197 | 197 | // input script: 0x160014{20-byte-key-hash} = 23 bytes
|
198 | 198 | // witness: <signature> <pubkey> = 72, 33 bytes
|
199 | 199 | // https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki#p2wpkh-nested-in-bip16-p2sh
|
200 |
| - InputWeightPrediction::new(23, &[72, 33]), |
201 |
| - Some(_) => unimplemented!(), |
202 |
| - None => panic!("Input not finalized!"), |
| 200 | + Some(script) if script.is_witness_program() && script.is_p2wpkh() => |
| 201 | + Ok(InputWeightPrediction::new(23, &[72, 33])), |
| 202 | + // Other script or witness program. |
| 203 | + Some(_) => Err(InputWeightError::NotSupported), |
| 204 | + // No redeem script provided. Cannot determine the script type. |
| 205 | + None => Err(InputWeightError::NotFinalized), |
203 | 206 | },
|
204 |
| - P2wpkh => InputWeightPrediction::P2WPKH_MAX, |
205 |
| - P2wsh => unimplemented!(), |
206 |
| - P2tr => InputWeightPrediction::P2TR_KEY_NON_DEFAULT_SIGHASH, |
207 |
| - _ => panic!("Unknown address type!"), |
208 |
| - }; |
| 207 | + P2wpkh => Ok(InputWeightPrediction::P2WPKH_MAX), |
| 208 | + P2wsh => Err(InputWeightError::NotSupported), |
| 209 | + P2tr => Ok(InputWeightPrediction::P2TR_KEY_NON_DEFAULT_SIGHASH), |
| 210 | + _ => Err(AddressTypeError::UnknownAddressType.into()), |
| 211 | + }?; |
209 | 212 |
|
210 | 213 | // Lengths of txid, index and sequence: (32, 4, 4).
|
211 | 214 | let input_weight = iwp.weight() + Weight::from_non_witness_data_size(32 + 4 + 4);
|
212 |
| - input_weight |
| 215 | + Ok(input_weight) |
213 | 216 | }
|
214 | 217 | }
|
215 | 218 |
|
@@ -279,3 +282,68 @@ impl fmt::Display for PsbtInputsError {
|
279 | 282 | impl std::error::Error for PsbtInputsError {
|
280 | 283 | fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { Some(&self.error) }
|
281 | 284 | }
|
| 285 | + |
| 286 | +#[derive(Debug)] |
| 287 | +pub(crate) enum AddressTypeError { |
| 288 | + PrevTxOut(PrevTxOutError), |
| 289 | + InvalidScript(FromScriptError), |
| 290 | + UnknownAddressType, |
| 291 | +} |
| 292 | + |
| 293 | +impl fmt::Display for AddressTypeError { |
| 294 | + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
| 295 | + match self { |
| 296 | + AddressTypeError::PrevTxOut(_) => write!(f, "invalid previous transaction output"), |
| 297 | + AddressTypeError::InvalidScript(_) => write!(f, "invalid script"), |
| 298 | + AddressTypeError::UnknownAddressType => write!(f, "unknown address type"), |
| 299 | + } |
| 300 | + } |
| 301 | +} |
| 302 | + |
| 303 | +impl std::error::Error for AddressTypeError { |
| 304 | + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { |
| 305 | + match self { |
| 306 | + AddressTypeError::PrevTxOut(error) => Some(error), |
| 307 | + AddressTypeError::InvalidScript(error) => Some(error), |
| 308 | + AddressTypeError::UnknownAddressType => None, |
| 309 | + } |
| 310 | + } |
| 311 | +} |
| 312 | + |
| 313 | +impl From<PrevTxOutError> for AddressTypeError { |
| 314 | + fn from(value: PrevTxOutError) -> Self { AddressTypeError::PrevTxOut(value) } |
| 315 | +} |
| 316 | + |
| 317 | +impl From<FromScriptError> for AddressTypeError { |
| 318 | + fn from(value: FromScriptError) -> Self { AddressTypeError::InvalidScript(value) } |
| 319 | +} |
| 320 | + |
| 321 | +#[derive(Debug)] |
| 322 | +pub(crate) enum InputWeightError { |
| 323 | + AddressType(AddressTypeError), |
| 324 | + NotFinalized, |
| 325 | + NotSupported, |
| 326 | +} |
| 327 | + |
| 328 | +impl fmt::Display for InputWeightError { |
| 329 | + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
| 330 | + match self { |
| 331 | + InputWeightError::AddressType(_) => write!(f, "invalid address type"), |
| 332 | + InputWeightError::NotFinalized => write!(f, "input not finalized"), |
| 333 | + InputWeightError::NotSupported => write!(f, "weight prediction not supported"), |
| 334 | + } |
| 335 | + } |
| 336 | +} |
| 337 | + |
| 338 | +impl std::error::Error for InputWeightError { |
| 339 | + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { |
| 340 | + match self { |
| 341 | + InputWeightError::AddressType(error) => Some(error), |
| 342 | + InputWeightError::NotFinalized => None, |
| 343 | + InputWeightError::NotSupported => None, |
| 344 | + } |
| 345 | + } |
| 346 | +} |
| 347 | +impl From<AddressTypeError> for InputWeightError { |
| 348 | + fn from(value: AddressTypeError) -> Self { InputWeightError::AddressType(value) } |
| 349 | +} |
0 commit comments