WitnessVersion type
This commit is contained in:
parent
4e3c2c32fc
commit
64c1ec0b76
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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<_>>();
|
||||
|
|
Loading…
Reference in New Issue