WitnessVersion type

This commit is contained in:
Dr Maxim Orlovsky 2021-01-30 20:50:04 +01:00
parent 4e3c2c32fc
commit 64c1ec0b76
No known key found for this signature in database
GPG Key ID: FFC0250947E5C6F7
2 changed files with 215 additions and 51 deletions

View File

@ -41,6 +41,7 @@ use policy::DUST_RELAY_TX_FEE;
#[cfg(feature="bitcoinconsensus")] use OutPoint;
use util::ecdsa::PublicKey;
use util::address::WitnessVersion;
#[derive(Clone, Default, PartialOrd, Ord, PartialEq, Eq, Hash)]
/// A Bitcoin script
@ -268,23 +269,18 @@ impl Script {
/// Generates P2WPKH-type of scriptPubkey
pub fn new_v0_wpkh(pubkey_hash: &WPubkeyHash) -> Script {
Script::new_witness_program(::bech32::u5::try_from_u8(0).unwrap(), &pubkey_hash.to_vec())
Script::new_witness_program(WitnessVersion::V0, &pubkey_hash.to_vec())
}
/// Generates P2WSH-type of scriptPubkey with a given hash of the redeem script
pub fn new_v0_wsh(script_hash: &WScriptHash) -> Script {
Script::new_witness_program(::bech32::u5::try_from_u8(0).unwrap(), &script_hash.to_vec())
Script::new_witness_program(WitnessVersion::V0, &script_hash.to_vec())
}
/// Generates P2WSH-type of scriptPubkey with a given hash of the redeem script
pub fn new_witness_program(ver: ::bech32::u5, program: &[u8]) -> Script {
let mut verop = ver.to_u8();
assert!(verop <= 16, "incorrect witness version provided: {}", verop);
if verop > 0 {
verop = 0x50 + verop;
}
pub fn new_witness_program(version: WitnessVersion, program: &[u8]) -> Script {
Builder::new()
.push_opcode(verop.into())
.push_opcode(version.into())
.push_slice(&program)
.into_script()
}

View File

@ -36,20 +36,22 @@
use prelude::*;
use core::fmt;
use core::num::ParseIntError;
use core::str::FromStr;
#[cfg(feature = "std")] use std::error;
use bech32;
use hashes::Hash;
use hash_types::{PubkeyHash, WPubkeyHash, ScriptHash, WScriptHash};
use blockdata::script;
use blockdata::{script, opcodes};
use blockdata::constants::{PUBKEY_ADDRESS_PREFIX_MAIN, SCRIPT_ADDRESS_PREFIX_MAIN, PUBKEY_ADDRESS_PREFIX_TEST, SCRIPT_ADDRESS_PREFIX_TEST};
use network::constants::Network;
use util::base58;
use util::ecdsa;
use blockdata::script::Instruction;
/// Address error.
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)]
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum Error {
/// Base58 encoding error
Base58(base58::Error),
@ -59,6 +61,10 @@ pub enum Error {
EmptyBech32Payload,
/// Script version must be 0 to 16 inclusive
InvalidWitnessVersion(u8),
/// Unable to parse witness version from string
UnparsableWitnessVersion(ParseIntError),
/// Bitcoin script opcode does not match any known witness version, the script is malformed
MalformedWitnessVersion,
/// The witness program must be between 2 and 40 bytes in length.
InvalidWitnessProgramLength(usize),
/// A v0 witness program must be either of length 20 or 32.
@ -74,6 +80,8 @@ impl fmt::Display for Error {
Error::Bech32(ref e) => write!(f, "bech32: {}", e),
Error::EmptyBech32Payload => write!(f, "the bech32 payload was empty"),
Error::InvalidWitnessVersion(v) => write!(f, "invalid witness script version: {}", v),
Error::UnparsableWitnessVersion(ref e) => write!(f, "Incorrect format of a witness version byte: {}", e),
Error::MalformedWitnessVersion => f.write_str("bitcoin script opcode does not match any known witness version, the script is malformed"),
Error::InvalidWitnessProgramLength(l) => write!(f,
"the witness program must be between 2 and 40 bytes in length: length={}", l,
),
@ -93,6 +101,7 @@ impl ::std::error::Error for Error {
match *self {
Error::Base58(ref e) => Some(e),
Error::Bech32(ref e) => Some(e),
Error::UnparsableWitnessVersion(ref e) => Some(e),
_ => None,
}
}
@ -149,6 +158,176 @@ impl FromStr for AddressType {
}
}
/// Version of the WitnessProgram: first byte of `scriptPubkey` in
/// transaction output for transactions starting with opcodes ranging from 0
/// to 16 (inclusive).
///
/// Structure helps to limit possible version of the witness according to the
/// specification; if a plain `u8` type was be used instead it would mean that
/// version may be > 16, which is incorrect.
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
#[repr(u8)]
pub enum WitnessVersion {
/// Initial version of Witness Program. Used for P2WPKH and P2WPK outputs
V0 = 0,
/// Version of Witness Program used for Taproot P2TR outputs
V1 = 1,
/// Future (unsupported) version of Witness Program
V2 = 2,
/// Future (unsupported) version of Witness Program
V3 = 3,
/// Future (unsupported) version of Witness Program
V4 = 4,
/// Future (unsupported) version of Witness Program
V5 = 5,
/// Future (unsupported) version of Witness Program
V6 = 6,
/// Future (unsupported) version of Witness Program
V7 = 7,
/// Future (unsupported) version of Witness Program
V8 = 8,
/// Future (unsupported) version of Witness Program
V9 = 9,
/// Future (unsupported) version of Witness Program
V10 = 10,
/// Future (unsupported) version of Witness Program
V11 = 11,
/// Future (unsupported) version of Witness Program
V12 = 12,
/// Future (unsupported) version of Witness Program
V13 = 13,
/// Future (unsupported) version of Witness Program
V14 = 14,
/// Future (unsupported) version of Witness Program
V15 = 15,
/// Future (unsupported) version of Witness Program
V16 = 16,
}
/// Prints [`WitnessVersion`] number (from 0 to 16) as integer, without
/// any prefix or suffix.
impl fmt::Display for WitnessVersion {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", *self as u8)
}
}
impl FromStr for WitnessVersion {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let version = s.parse().map_err(|err| Error::UnparsableWitnessVersion(err))?;
WitnessVersion::from_num(version)
}
}
impl WitnessVersion {
/// Converts 5-bit unsigned integer value matching single symbol from Bech32(m) address encoding
/// ([`bech32::u5`]) into [`WitnessVersion`] variant.
///
/// # Returns
/// Version of the Witness program
///
/// # Errors
/// If the integer does not corresponds to any witness version, errors with
/// [`Error::InvalidWitnessVersion`]
pub fn from_u5(value: ::bech32::u5) -> Result<Self, Error> {
WitnessVersion::from_num(value.to_u8())
}
/// Converts 8-bit unsigned integer value into [`WitnessVersion`] variant.
///
/// # Returns
/// Version of the Witness program
///
/// # Errors
/// If the integer does not corresponds to any witness version, errors with
/// [`Error::InvalidWitnessVersion`]
pub fn from_num(no: u8) -> Result<Self, Error> {
Ok(match no {
0 => WitnessVersion::V0,
1 => WitnessVersion::V1,
2 => WitnessVersion::V2,
3 => WitnessVersion::V3,
4 => WitnessVersion::V4,
5 => WitnessVersion::V5,
6 => WitnessVersion::V6,
7 => WitnessVersion::V7,
8 => WitnessVersion::V8,
9 => WitnessVersion::V9,
10 => WitnessVersion::V10,
11 => WitnessVersion::V11,
12 => WitnessVersion::V12,
13 => WitnessVersion::V13,
14 => WitnessVersion::V14,
15 => WitnessVersion::V15,
16 => WitnessVersion::V16,
wrong => Err(Error::InvalidWitnessVersion(wrong))?,
})
}
/// Converts bitcoin script opcode into [`WitnessVersion`] variant.
///
/// # Returns
/// Version of the Witness program (for opcodes in range of `OP_0`..`OP_16`)
///
/// # Errors
/// If the opcode does not correspond to any witness version, errors with
/// [`Error::UnparsableWitnessVersion`]
pub fn from_opcode(opcode: opcodes::All) -> Result<Self, Error> {
match opcode.into_u8() {
0 => Ok(WitnessVersion::V0),
version if version >= opcodes::all::OP_PUSHNUM_1.into_u8() && version <= opcodes::all::OP_PUSHNUM_16.into_u8() =>
WitnessVersion::from_num(version - opcodes::all::OP_PUSHNUM_1.into_u8() + 1),
_ => Err(Error::MalformedWitnessVersion)
}
}
/// Converts bitcoin script [`Instruction`] (parsed opcode) into [`WitnessVersion`] variant.
///
/// # Returns
/// Version of the Witness program for [`Instruction::Op`] and [`Instruction::PushBytes`] with
/// byte value within `1..=16` range.
///
/// # Errors
/// If the opcode does not correspond to any witness version, errors with
/// [`Error::UnparsableWitnessVersion`] for the rest of opcodes
pub fn from_instruction(instruction: Instruction) -> Result<Self, Error> {
match instruction {
Instruction::Op(op) => WitnessVersion::from_opcode(op),
Instruction::PushBytes(bytes) if bytes.len() == 0 => Ok(WitnessVersion::V0),
Instruction::PushBytes(_) => Err(Error::MalformedWitnessVersion),
}
}
/// Returns integer version number representation for a given [`WitnessVersion`] value.
///
/// NB: this is not the same as an integer representation of the opcode signifying witness
/// version in bitcoin script. Thus, there is no function to directly convert witness version
/// into a byte since the conversion requires context (bitcoin script or just a version number)
pub fn into_num(self) -> u8 {
self as u8
}
}
impl From<WitnessVersion> for ::bech32::u5 {
/// Converts [`WitnessVersion`] instance into corresponding Bech32(m) u5-value ([`bech32::u5`])
fn from(version: WitnessVersion) -> Self {
::bech32::u5::try_from_u8(version.into_num()).expect("WitnessVersion must be 0..=16")
}
}
impl From<WitnessVersion> for opcodes::All {
/// Converts [`WitnessVersion`] instance into corresponding Bitcoin scriptopcode (`OP_0`..`OP_16`)
fn from(version: WitnessVersion) -> opcodes::All {
match version {
WitnessVersion::V0 => opcodes::all::OP_PUSHBYTES_0,
no => opcodes::All::from(opcodes::all::OP_PUSHNUM_1.into_u8() + no.into_num() - 1)
}
}
}
/// The method used to produce an address
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Payload {
@ -159,7 +338,7 @@ pub enum Payload {
/// Segwit addresses
WitnessProgram {
/// The witness program version
version: bech32::u5,
version: WitnessVersion,
/// The witness program
program: Vec<u8>,
},
@ -169,23 +348,17 @@ impl Payload {
/// Get a [Payload] from an output script (scriptPubkey).
pub fn from_script(script: &script::Script) -> Option<Payload> {
Some(if script.is_p2pkh() {
Payload::PubkeyHash(PubkeyHash::from_slice(&script.as_bytes()[3..23]).unwrap())
let mut hash_inner = [0u8; 20];
hash_inner.copy_from_slice(&script.as_bytes()[3..23]);
Payload::PubkeyHash(PubkeyHash::from_inner(hash_inner))
} else if script.is_p2sh() {
Payload::ScriptHash(ScriptHash::from_slice(&script.as_bytes()[2..22]).unwrap())
let mut hash_inner = [0u8; 20];
hash_inner.copy_from_slice(&script.as_bytes()[2..22]);
Payload::ScriptHash(ScriptHash::from_inner(hash_inner))
} else if script.is_witness_program() {
// We can unwrap the u5 check and assume script length
// because [Script::is_witness_program] makes sure of this.
Payload::WitnessProgram {
version: {
// Since we passed the [is_witness_program] check,
// the first byte is either 0x00 or 0x50 + version.
let mut verop = script.as_bytes()[0];
if verop > 0x50 {
verop -= 0x50;
}
bech32::u5::try_from_u8(verop).expect("checked before")
},
program: script.as_bytes()[2..].to_vec(),
version: WitnessVersion::from_opcode(opcodes::All::from(script[0])).ok()?,
program: script[2..].to_vec(),
}
} else {
return None;
@ -200,9 +373,9 @@ impl Payload {
Payload::ScriptHash(ref hash) =>
script::Script::new_p2sh(hash),
Payload::WitnessProgram {
version: ver,
version,
program: ref prog,
} => script::Script::new_witness_program(ver, prog)
} => script::Script::new_witness_program(version, prog)
}
}
}
@ -256,7 +429,7 @@ impl Address {
Ok(Address {
network: network,
payload: Payload::WitnessProgram {
version: bech32::u5::try_from_u8(0).expect("0<32"),
version: WitnessVersion::V0,
program: WPubkeyHash::from_engine(hash_engine)[..].to_vec(),
},
})
@ -289,7 +462,7 @@ impl Address {
Address {
network: network,
payload: Payload::WitnessProgram {
version: bech32::u5::try_from_u8(0).expect("0<32"),
version: WitnessVersion::V0,
program: WScriptHash::hash(&script[..])[..].to_vec(),
},
}
@ -310,18 +483,18 @@ impl Address {
}
/// Get the address type of the address.
/// None if unknown or non-standard.
/// None if unknown, non-standard or related to the future witness version.
pub fn address_type(&self) -> Option<AddressType> {
match self.payload {
Payload::PubkeyHash(_) => Some(AddressType::P2pkh),
Payload::ScriptHash(_) => Some(AddressType::P2sh),
Payload::WitnessProgram {
version: ver,
version,
program: ref prog,
} => {
// BIP-141 p2wpkh or p2wsh addresses.
match ver.to_u8() {
0 => match prog.len() {
match version {
WitnessVersion::V0 => match prog.len() {
20 => Some(AddressType::P2wpkh),
32 => Some(AddressType::P2wsh),
_ => None,
@ -335,7 +508,7 @@ impl Address {
/// Check whether or not the address is following Bitcoin
/// standardness rules.
///
/// Segwit addresses with unassigned witness versions or non-standard
/// SegWit addresses with unassigned witness versions or non-standard
/// program sizes are considered non-standard.
pub fn is_standard(&self) -> bool {
self.address_type().is_some()
@ -428,7 +601,7 @@ impl fmt::Display for Address {
base58::check_encode_slice_to_fmt(fmt, &prefixed[..])
}
Payload::WitnessProgram {
version: ver,
version,
program: ref prog,
} => {
let hrp = match self.network {
@ -436,7 +609,7 @@ impl fmt::Display for Address {
Network::Testnet | Network::Signet => "tb",
Network::Regtest => "bcrt",
};
let bech_ver = if ver.to_u8() > 0 { bech32::Variant::Bech32m } else { bech32::Variant::Bech32 };
let bech_ver = if version.into_num() > 0 { bech32::Variant::Bech32m } else { bech32::Variant::Bech32 };
let mut upper_writer;
let writer = if fmt.alternate() {
upper_writer = UpperWriter(fmt);
@ -445,7 +618,7 @@ impl fmt::Display for Address {
fmt as &mut dyn fmt::Write
};
let mut bech32_writer = bech32::Bech32Writer::new(hrp, bech_ver, writer)?;
bech32::WriteBase32::write_u5(&mut bech32_writer, ver)?;
bech32::WriteBase32::write_u5(&mut bech32_writer, version.into())?;
bech32::ToBase32::write_base32(&prog, &mut bech32_writer)
}
}
@ -493,28 +666,24 @@ impl FromStr for Address {
}
// Get the script version and program (converted from 5-bit to 8-bit)
let (version, program): (bech32::u5, Vec<u8>) = {
let (version, program): (WitnessVersion, Vec<u8>) = {
let (v, p5) = payload.split_at(1);
(v[0], bech32::FromBase32::from_base32(p5)?)
(WitnessVersion::from_u5(v[0])?, bech32::FromBase32::from_base32(p5)?)
};
// Generic segwit checks.
if version.to_u8() > 16 {
return Err(Error::InvalidWitnessVersion(version.to_u8()));
}
if program.len() < 2 || program.len() > 40 {
return Err(Error::InvalidWitnessProgramLength(program.len()));
}
// Specific segwit v0 check.
if version.to_u8() == 0 && (program.len() != 20 && program.len() != 32) {
if version == WitnessVersion::V0 && (program.len() != 20 && program.len() != 32) {
return Err(Error::InvalidSegwitV0ProgramLength(program.len()));
}
// Bech32 encoding check
if (version.to_u8() > 0 && variant != bech32::Variant::Bech32m) ||
(version.to_u8() == 0 && variant != bech32::Variant::Bech32) {
return Err(Error::InvalidWitnessVersion(version.to_u8()))
if (version.into_num() > 0 && variant != bech32::Variant::Bech32m) ||
(version.into_num() == 0 && variant != bech32::Variant::Bech32) {
return Err(Error::InvalidWitnessVersion(version.into_num()))
}
return Ok(Address {
@ -710,14 +879,13 @@ mod tests {
#[test]
fn test_non_existent_segwit_version() {
let version = 13;
// 40-byte program
let program = hex!(
"654f6ea368e0acdfd92976b7c2103a1b26313f430654f6ea368e0acdfd92976b7c2103a1b26313f4"
);
let addr = Address {
payload: Payload::WitnessProgram {
version: bech32::u5::try_from_u8(version).expect("0<32"),
version: WitnessVersion::V13,
program: program,
},
network: Network::Bitcoin,
@ -887,7 +1055,7 @@ mod tests {
];
let segwit_payload = (0..=16).map(|version| {
Payload::WitnessProgram {
version: bech32::u5::try_from_u8(version).unwrap(),
version: WitnessVersion::from_num(version).unwrap(),
program: vec![]
}
}).collect::<Vec<_>>();