use core::fmt;
use hashes::Hash as _;
use internals::array_vec::ArrayVec;
use secp256k1::{Secp256k1, Verification};
use crate::blockdata::script::witness_version::WitnessVersion;
use crate::blockdata::script::{PushBytes, Script};
use crate::crypto::key::{CompressedPublicKey, TapTweak, TweakedPublicKey, UntweakedPublicKey};
use crate::taproot::TapNodeHash;
pub const MIN_SIZE: usize = 2;
pub const MAX_SIZE: usize = 40;
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct WitnessProgram {
version: WitnessVersion,
program: ArrayVec<u8, MAX_SIZE>,
}
impl WitnessProgram {
pub fn new(version: WitnessVersion, bytes: &[u8]) -> Result<Self, Error> {
use Error::*;
let program_len = bytes.len();
if program_len < MIN_SIZE || program_len > MAX_SIZE {
return Err(InvalidLength(program_len));
}
if version == WitnessVersion::V0 && (program_len != 20 && program_len != 32) {
return Err(InvalidSegwitV0Length(program_len));
}
let program = ArrayVec::from_slice(bytes);
Ok(WitnessProgram { version, program })
}
fn new_p2wpkh(program: [u8; 20]) -> Self {
WitnessProgram { version: WitnessVersion::V0, program: ArrayVec::from_slice(&program) }
}
fn new_p2wsh(program: [u8; 32]) -> Self {
WitnessProgram { version: WitnessVersion::V0, program: ArrayVec::from_slice(&program) }
}
fn new_p2tr(program: [u8; 32]) -> Self {
WitnessProgram { version: WitnessVersion::V1, program: ArrayVec::from_slice(&program) }
}
pub fn p2wpkh(pk: &CompressedPublicKey) -> Self {
let hash = pk.wpubkey_hash();
WitnessProgram::new_p2wpkh(hash.to_byte_array())
}
pub fn p2wsh(script: &Script) -> Self {
let hash = script.wscript_hash();
WitnessProgram::new_p2wsh(hash.to_byte_array())
}
pub fn p2tr<C: Verification>(
secp: &Secp256k1<C>,
internal_key: UntweakedPublicKey,
merkle_root: Option<TapNodeHash>,
) -> Self {
let (output_key, _parity) = internal_key.tap_tweak(secp, merkle_root);
let pubkey = output_key.to_inner().serialize();
WitnessProgram::new_p2tr(pubkey)
}
pub fn p2tr_tweaked(output_key: TweakedPublicKey) -> Self {
let pubkey = output_key.to_inner().serialize();
WitnessProgram::new_p2tr(pubkey)
}
pub fn version(&self) -> WitnessVersion { self.version }
pub fn program(&self) -> &PushBytes {
self.program
.as_slice()
.try_into()
.expect("witness programs are always smaller than max size of PushBytes")
}
pub fn is_p2wpkh(&self) -> bool {
self.version == WitnessVersion::V0 && self.program.len() == 20
}
pub fn is_p2wsh(&self) -> bool {
self.version == WitnessVersion::V0 && self.program.len() == 32
}
pub fn is_p2tr(&self) -> bool { self.version == WitnessVersion::V1 && self.program.len() == 32 }
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum Error {
InvalidLength(usize),
InvalidSegwitV0Length(usize),
}
internals::impl_from_infallible!(Error);
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use Error::*;
match *self {
InvalidLength(len) =>
write!(f, "witness program must be between 2 and 40 bytes: length={}", len),
InvalidSegwitV0Length(len) =>
write!(f, "a v0 witness program must be either 20 or 32 bytes: length={}", len),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
use Error::*;
match *self {
InvalidLength(_) | InvalidSegwitV0Length(_) => None,
}
}
}