Merge pull request #617 from LNP-BP/feat/witness-version

WitnessVersion type
This commit is contained in:
Andrew Poelstra 2021-09-08 20:58:57 +00:00 committed by GitHub
commit 2a655f4b58
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 225 additions and 61 deletions

View File

@ -41,6 +41,7 @@ use policy::DUST_RELAY_TX_FEE;
#[cfg(feature="bitcoinconsensus")] use OutPoint; #[cfg(feature="bitcoinconsensus")] use OutPoint;
use util::ecdsa::PublicKey; use util::ecdsa::PublicKey;
use util::address::WitnessVersion;
#[derive(Clone, Default, PartialOrd, Ord, PartialEq, Eq, Hash)] #[derive(Clone, Default, PartialOrd, Ord, PartialEq, Eq, Hash)]
/// A Bitcoin script /// A Bitcoin script
@ -268,23 +269,18 @@ impl Script {
/// Generates P2WPKH-type of scriptPubkey /// Generates P2WPKH-type of scriptPubkey
pub fn new_v0_wpkh(pubkey_hash: &WPubkeyHash) -> Script { 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 /// Generates P2WSH-type of scriptPubkey with a given hash of the redeem script
pub fn new_v0_wsh(script_hash: &WScriptHash) -> 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 /// Generates P2WSH-type of scriptPubkey with a given hash of the redeem script
pub fn new_witness_program(ver: ::bech32::u5, program: &[u8]) -> Script { pub fn new_witness_program(version: WitnessVersion, program: &[u8]) -> Script {
let mut verop = ver.to_u8();
assert!(verop <= 16, "incorrect witness version provided: {}", verop);
if verop > 0 {
verop = 0x50 + verop;
}
Builder::new() Builder::new()
.push_opcode(verop.into()) .push_opcode(version.into())
.push_slice(&program) .push_slice(&program)
.into_script() .into_script()
} }
@ -371,17 +367,17 @@ impl Script {
// push opcode (for 0 to 16) followed by a data push between 2 and 40 bytes gets a new // push opcode (for 0 to 16) followed by a data push between 2 and 40 bytes gets a new
// special meaning. The value of the first push is called the "version byte". The following // special meaning. The value of the first push is called the "version byte". The following
// byte vector pushed is called the "witness program". // byte vector pushed is called the "witness program".
let min_vernum: u8 = opcodes::all::OP_PUSHNUM_1.into_u8(); let script_len = self.0.len();
let max_vernum: u8 = opcodes::all::OP_PUSHNUM_16.into_u8(); if script_len < 4 || script_len > 42 {
self.0.len() >= 4 return false
&& self.0.len() <= 42 }
// Version 0 or PUSHNUM_1-PUSHNUM_16 let ver_opcode = opcodes::All::from(self.0[0]); // Version 0 or PUSHNUM_1-PUSHNUM_16
&& (self.0[0] == 0 || self.0[0] >= min_vernum && self.0[0] <= max_vernum) let push_opbyte = self.0[1]; // Second byte push opcode 2-40 bytes
// Second byte push opcode 2-40 bytes WitnessVersion::from_opcode(ver_opcode).is_ok()
&& self.0[1] >= opcodes::all::OP_PUSHBYTES_2.into_u8() && push_opbyte >= opcodes::all::OP_PUSHBYTES_2.into_u8()
&& self.0[1] <= opcodes::all::OP_PUSHBYTES_40.into_u8() && push_opbyte <= opcodes::all::OP_PUSHBYTES_40.into_u8()
// Check that the rest of the script has the correct size // Check that the rest of the script has the correct size
&& self.0.len() - 2 == self.0[1] as usize && script_len - 2 == push_opbyte as usize
} }
/// Checks whether a script pubkey is a p2wsh output /// Checks whether a script pubkey is a p2wsh output

View File

@ -36,20 +36,22 @@
use prelude::*; use prelude::*;
use core::fmt; use core::fmt;
use core::num::ParseIntError;
use core::str::FromStr; use core::str::FromStr;
#[cfg(feature = "std")] use std::error; #[cfg(feature = "std")] use std::error;
use bech32; use bech32;
use hashes::Hash; use hashes::Hash;
use hash_types::{PubkeyHash, WPubkeyHash, ScriptHash, WScriptHash}; 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 blockdata::constants::{PUBKEY_ADDRESS_PREFIX_MAIN, SCRIPT_ADDRESS_PREFIX_MAIN, PUBKEY_ADDRESS_PREFIX_TEST, SCRIPT_ADDRESS_PREFIX_TEST};
use network::constants::Network; use network::constants::Network;
use util::base58; use util::base58;
use util::ecdsa; use util::ecdsa;
use blockdata::script::Instruction;
/// Address error. /// Address error.
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)] #[derive(Debug, PartialEq, Eq, Clone)]
pub enum Error { pub enum Error {
/// Base58 encoding error /// Base58 encoding error
Base58(base58::Error), Base58(base58::Error),
@ -59,6 +61,10 @@ pub enum Error {
EmptyBech32Payload, EmptyBech32Payload,
/// Script version must be 0 to 16 inclusive /// Script version must be 0 to 16 inclusive
InvalidWitnessVersion(u8), 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. /// The witness program must be between 2 and 40 bytes in length.
InvalidWitnessProgramLength(usize), InvalidWitnessProgramLength(usize),
/// A v0 witness program must be either of length 20 or 32. /// 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::Bech32(ref e) => write!(f, "bech32: {}", e),
Error::EmptyBech32Payload => write!(f, "the bech32 payload was empty"), Error::EmptyBech32Payload => write!(f, "the bech32 payload was empty"),
Error::InvalidWitnessVersion(v) => write!(f, "invalid witness script version: {}", v), 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, Error::InvalidWitnessProgramLength(l) => write!(f,
"the witness program must be between 2 and 40 bytes in length: length={}", l, "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 { match *self {
Error::Base58(ref e) => Some(e), Error::Base58(ref e) => Some(e),
Error::Bech32(ref e) => Some(e), Error::Bech32(ref e) => Some(e),
Error::UnparsableWitnessVersion(ref e) => Some(e),
_ => None, _ => 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 /// The method used to produce an address
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Payload { pub enum Payload {
@ -159,7 +338,7 @@ pub enum Payload {
/// Segwit addresses /// Segwit addresses
WitnessProgram { WitnessProgram {
/// The witness program version /// The witness program version
version: bech32::u5, version: WitnessVersion,
/// The witness program /// The witness program
program: Vec<u8>, program: Vec<u8>,
}, },
@ -169,23 +348,17 @@ impl Payload {
/// Get a [Payload] from an output script (scriptPubkey). /// Get a [Payload] from an output script (scriptPubkey).
pub fn from_script(script: &script::Script) -> Option<Payload> { pub fn from_script(script: &script::Script) -> Option<Payload> {
Some(if script.is_p2pkh() { 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() { } 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() { } 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 { Payload::WitnessProgram {
version: { version: WitnessVersion::from_opcode(opcodes::All::from(script[0])).ok()?,
// Since we passed the [is_witness_program] check, program: script[2..].to_vec(),
// 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(),
} }
} else { } else {
return None; return None;
@ -200,9 +373,9 @@ impl Payload {
Payload::ScriptHash(ref hash) => Payload::ScriptHash(ref hash) =>
script::Script::new_p2sh(hash), script::Script::new_p2sh(hash),
Payload::WitnessProgram { Payload::WitnessProgram {
version: ver, version,
program: ref prog, 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 { Ok(Address {
network: network, network: network,
payload: Payload::WitnessProgram { payload: Payload::WitnessProgram {
version: bech32::u5::try_from_u8(0).expect("0<32"), version: WitnessVersion::V0,
program: WPubkeyHash::from_engine(hash_engine)[..].to_vec(), program: WPubkeyHash::from_engine(hash_engine)[..].to_vec(),
}, },
}) })
@ -289,7 +462,7 @@ impl Address {
Address { Address {
network: network, network: network,
payload: Payload::WitnessProgram { payload: Payload::WitnessProgram {
version: bech32::u5::try_from_u8(0).expect("0<32"), version: WitnessVersion::V0,
program: WScriptHash::hash(&script[..])[..].to_vec(), program: WScriptHash::hash(&script[..])[..].to_vec(),
}, },
} }
@ -310,18 +483,18 @@ impl Address {
} }
/// Get the address type of the 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> { pub fn address_type(&self) -> Option<AddressType> {
match self.payload { match self.payload {
Payload::PubkeyHash(_) => Some(AddressType::P2pkh), Payload::PubkeyHash(_) => Some(AddressType::P2pkh),
Payload::ScriptHash(_) => Some(AddressType::P2sh), Payload::ScriptHash(_) => Some(AddressType::P2sh),
Payload::WitnessProgram { Payload::WitnessProgram {
version: ver, version,
program: ref prog, program: ref prog,
} => { } => {
// BIP-141 p2wpkh or p2wsh addresses. // BIP-141 p2wpkh or p2wsh addresses.
match ver.to_u8() { match version {
0 => match prog.len() { WitnessVersion::V0 => match prog.len() {
20 => Some(AddressType::P2wpkh), 20 => Some(AddressType::P2wpkh),
32 => Some(AddressType::P2wsh), 32 => Some(AddressType::P2wsh),
_ => None, _ => None,
@ -335,7 +508,7 @@ impl Address {
/// Check whether or not the address is following Bitcoin /// Check whether or not the address is following Bitcoin
/// standardness rules. /// 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. /// program sizes are considered non-standard.
pub fn is_standard(&self) -> bool { pub fn is_standard(&self) -> bool {
self.address_type().is_some() self.address_type().is_some()
@ -428,7 +601,7 @@ impl fmt::Display for Address {
base58::check_encode_slice_to_fmt(fmt, &prefixed[..]) base58::check_encode_slice_to_fmt(fmt, &prefixed[..])
} }
Payload::WitnessProgram { Payload::WitnessProgram {
version: ver, version,
program: ref prog, program: ref prog,
} => { } => {
let hrp = match self.network { let hrp = match self.network {
@ -436,7 +609,7 @@ impl fmt::Display for Address {
Network::Testnet | Network::Signet => "tb", Network::Testnet | Network::Signet => "tb",
Network::Regtest => "bcrt", 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 mut upper_writer;
let writer = if fmt.alternate() { let writer = if fmt.alternate() {
upper_writer = UpperWriter(fmt); upper_writer = UpperWriter(fmt);
@ -445,7 +618,7 @@ impl fmt::Display for Address {
fmt as &mut dyn fmt::Write fmt as &mut dyn fmt::Write
}; };
let mut bech32_writer = bech32::Bech32Writer::new(hrp, bech_ver, writer)?; 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) 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) // 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); 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 { if program.len() < 2 || program.len() > 40 {
return Err(Error::InvalidWitnessProgramLength(program.len())); return Err(Error::InvalidWitnessProgramLength(program.len()));
} }
// Specific segwit v0 check. // 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())); return Err(Error::InvalidSegwitV0ProgramLength(program.len()));
} }
// Bech32 encoding check // Bech32 encoding check
if (version.to_u8() > 0 && variant != bech32::Variant::Bech32m) || if (version.into_num() > 0 && variant != bech32::Variant::Bech32m) ||
(version.to_u8() == 0 && variant != bech32::Variant::Bech32) { (version.into_num() == 0 && variant != bech32::Variant::Bech32) {
return Err(Error::InvalidWitnessVersion(version.to_u8())) return Err(Error::InvalidWitnessVersion(version.into_num()))
} }
return Ok(Address { return Ok(Address {
@ -710,14 +879,13 @@ mod tests {
#[test] #[test]
fn test_non_existent_segwit_version() { fn test_non_existent_segwit_version() {
let version = 13;
// 40-byte program // 40-byte program
let program = hex!( let program = hex!(
"654f6ea368e0acdfd92976b7c2103a1b26313f430654f6ea368e0acdfd92976b7c2103a1b26313f4" "654f6ea368e0acdfd92976b7c2103a1b26313f430654f6ea368e0acdfd92976b7c2103a1b26313f4"
); );
let addr = Address { let addr = Address {
payload: Payload::WitnessProgram { payload: Payload::WitnessProgram {
version: bech32::u5::try_from_u8(version).expect("0<32"), version: WitnessVersion::V13,
program: program, program: program,
}, },
network: Network::Bitcoin, network: Network::Bitcoin,
@ -887,7 +1055,7 @@ mod tests {
]; ];
let segwit_payload = (0..=16).map(|version| { let segwit_payload = (0..=16).map(|version| {
Payload::WitnessProgram { Payload::WitnessProgram {
version: bech32::u5::try_from_u8(version).unwrap(), version: WitnessVersion::from_num(version).unwrap(),
program: vec![] program: vec![]
} }
}).collect::<Vec<_>>(); }).collect::<Vec<_>>();