use internals::array_vec::ArrayVec;
use primitives::script::{
validate_witness_program, ParsedWitnessProgram, WitnessProgramClass as PrimitiveClass,
};
use super::witness_version::WitnessVersion;
use super::{PushBytes, WScriptHash, WitnessScript, WitnessScriptSizeError};
use crate::crypto::key::WPubkeyHash;
use crate::script::WitnessScriptExt as _;
pub const MIN_SIZE: usize = primitives::script::WITNESS_PROGRAM_MIN_SIZE;
pub const MAX_SIZE: usize = primitives::script::WITNESS_PROGRAM_MAX_SIZE;
pub(crate) use primitives::script::P2A_PROGRAM;
#[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> {
validate_witness_program(version.to_num(), bytes.len())?;
let program = ArrayVec::from_slice(bytes);
Ok(Self { version, program })
}
fn new_p2wpkh(program: [u8; 20]) -> Self {
Self { version: WitnessVersion::V0, program: ArrayVec::from_slice(&program) }
}
fn new_p2wsh(program: [u8; 32]) -> Self {
Self { version: WitnessVersion::V0, program: ArrayVec::from_slice(&program) }
}
fn new_p2wsh512(program: [u8; 64]) -> Self {
Self { version: WitnessVersion::V1, program: ArrayVec::from_slice(&program) }
}
pub fn p2wpkh(pubkey_hash: WPubkeyHash) -> Self {
Self::new_p2wpkh(pubkey_hash.to_byte_array())
}
pub fn p2wsh(script: &WitnessScript) -> Result<Self, WitnessScriptSizeError> {
script.wscript_hash().map(Self::p2wsh_from_hash)
}
pub fn p2wsh_from_hash(hash: WScriptHash) -> Self {
Self::new_p2wsh(hash.to_byte_array())
}
pub fn p2wsh512(hash: [u8; 64]) -> Self {
Self::new_p2wsh512(hash)
}
pub const fn p2a() -> Self {
Self { version: WitnessVersion::V1, program: ArrayVec::from_slice(&P2A_PROGRAM) }
}
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.class() == PrimitiveClass::P2wpkh
}
pub fn is_p2wsh(&self) -> bool {
self.class() == PrimitiveClass::P2wsh
}
pub fn is_p2wsh512(&self) -> bool {
self.class() == PrimitiveClass::P2wsh512
}
pub fn is_p2a(&self) -> bool {
self.class() == PrimitiveClass::P2a
}
fn class(&self) -> PrimitiveClass {
ParsedWitnessProgram::from_program(self.version.to_num(), self.program.as_slice()).class()
}
}
pub type Error = primitives::script::WitnessProgramError;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn witness_program_is_too_short() {
let arbitrary_bytes = [0x00; MIN_SIZE - 1];
assert!(WitnessProgram::new(WitnessVersion::V15, &arbitrary_bytes).is_err());
}
#[test]
fn witness_program_is_too_long() {
let arbitrary_bytes = [0x00; MAX_SIZE + 1];
assert!(WitnessProgram::new(WitnessVersion::V15, &arbitrary_bytes).is_err());
}
#[test]
fn valid_v0_witness_programs() {
let arbitrary_bytes = [0x00; MAX_SIZE];
for size in MIN_SIZE..=40 {
let program = WitnessProgram::new(WitnessVersion::V0, &arbitrary_bytes[..size]);
if size == 20 {
assert!(program.expect("valid witness program").is_p2wpkh());
continue;
}
if size == 32 {
assert!(program.expect("valid witness program").is_p2wsh());
continue;
}
assert!(program.is_err());
}
}
#[test]
fn valid_v1_witness_programs() {
let p2a_bytes = [78, 115];
assert!(WitnessProgram::new(WitnessVersion::V1, &p2a_bytes)
.expect("valid witness program")
.is_p2a());
let arbitrary_64 = [0x00; 64];
assert!(WitnessProgram::new(WitnessVersion::V1, &arbitrary_64)
.expect("valid witness program")
.is_p2wsh512());
}
#[test]
fn v1_32_byte_program_remains_unclassified() {
let arbitrary_bytes = [0x00; 32];
let program = WitnessProgram::new(WitnessVersion::V1, &arbitrary_bytes)
.expect("valid witness program");
assert_eq!(program.version(), WitnessVersion::V1);
assert!(!program.is_p2a());
assert!(!program.is_p2wsh512());
}
}