diff --git a/Cargo-latest.lock b/Cargo-latest.lock index 426e4104..180d9781 100644 --- a/Cargo-latest.lock +++ b/Cargo-latest.lock @@ -190,7 +190,7 @@ checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "elements" -version = "0.26.1" +version = "0.26.2" dependencies = [ "bech32", "bincode", diff --git a/Cargo.toml b/Cargo.toml index 5b5f627f..a644862b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "elements" -version = "0.26.1" +version = "0.26.2" authors = ["Andrew Poelstra "] description = "Library with support for de/serialization, parsing and executing on data structures and network messages related to Elements" license = "CC0-1.0" diff --git a/src/address.rs b/src/address.rs index 296fa63b..27fccb9e 100644 --- a/src/address.rs +++ b/src/address.rs @@ -26,6 +26,8 @@ use crate::blech32::{Blech32, Blech32m}; use crate::hashes::Hash; use bitcoin::base58; use bitcoin::PublicKey; +use crate::internals::array::ArrayExt as _; +use crate::internals::slice::SliceExt; use secp256k1_zkp; use secp256k1_zkp::Secp256k1; use secp256k1_zkp::Verification; @@ -470,13 +472,17 @@ impl Address { }; let (blinding_pubkey, program) = match blinded { - true => ( + true => { + let (pk, rest) = SliceExt::split_first_chunk::<33>(data.as_slice()) + .ok_or(AddressError::InvalidSegwitV0Encoding)?; + ( Some( - secp256k1_zkp::PublicKey::from_slice(&data[..33]) + secp256k1_zkp::PublicKey::from_slice(pk) .map_err(AddressError::InvalidBlindingPubKey)?, - ), - data[33..].to_vec(), - ), + ), + rest.to_vec(), + ) + }, false => (None, data), }; @@ -489,35 +495,41 @@ impl Address { // data.len() should be >= 1 when this method is called fn from_base58(data: &[u8], params: &'static AddressParams) -> Result { + let len_error = AddressError::InvalidLength(data.len()); // When unblinded, the structure is: // <1: regular prefix> <20: hash160> // When blinded, the structure is: // <1: blinding prefix> <1: regular prefix> <33: blinding pubkey> <20: hash160> - let blinded = data[0] == params.blinded_prefix; - let prefix = match (blinded, data.len()) { - (true, 55) => data[1], - (false, 21) => data[0], - (_, len) => return Err(AddressError::InvalidLength(len)), + let (blinding_prefix, blinded_data) = match data.split_first() { + Some(v) => v, + None => return Err(len_error), }; - let (blinding_pubkey, payload_data) = match blinded { - true => ( - Some( - secp256k1_zkp::PublicKey::from_slice(&data[2..35]) - .map_err(AddressError::InvalidBlindingPubKey)?, - ), - &data[35..], - ), - false => (None, &data[1..]), + let (prefix, blinding_pubkey, hash) = if *blinding_prefix == params.blinded_prefix { + let (prefix, pubkey_and_hash) = match blinded_data.split_first() { + Some(v) => v, + None => return Err(len_error), + }; + + let pubkey_and_hash = <&[u8; 53]>::try_from(pubkey_and_hash).map_err(|_| len_error)?; + let (pubkey, hash) = pubkey_and_hash.split_array::<33, 20>(); + + let blinding_pubkey = secp256k1_zkp::PublicKey::from_slice(pubkey) + .map_err(AddressError::InvalidBlindingPubKey)?; + + (prefix, Some(blinding_pubkey), hash) + } else { + let hash = <&[u8; 20]>::try_from(blinded_data).map_err(|_| len_error)?; + (blinding_prefix, None, hash) }; - let payload = if prefix == params.p2pkh_prefix { - Payload::PubkeyHash(PubkeyHash::from_slice(payload_data).unwrap()) - } else if prefix == params.p2sh_prefix { - Payload::ScriptHash(ScriptHash::from_slice(payload_data).unwrap()) + let payload = if *prefix == params.p2pkh_prefix { + Payload::PubkeyHash(PubkeyHash::from_byte_array(*hash)) + } else if *prefix == params.p2sh_prefix { + Payload::ScriptHash(ScriptHash::from_byte_array(*hash)) } else { - return Err(AddressError::InvalidAddressVersion(prefix)); + return Err(AddressError::InvalidAddressVersion(*prefix)); }; Ok(Address { diff --git a/src/blind.rs b/src/blind.rs index 424a4e7d..71f7e899 100644 --- a/src/blind.rs +++ b/src/blind.rs @@ -15,6 +15,8 @@ //! # Transactions Blinding //! +use crate::internals::array::ArrayExt as _; +use crate::internals::slice::SliceExt; use std::{self, collections::BTreeMap, fmt}; use secp256k1_zkp::{ @@ -775,15 +777,31 @@ impl TxOut { additional_generator, )?; - let (asset, asset_bf) = opening.message.as_ref().split_at(32); - let asset = AssetId::from_slice(asset)?; - let asset_bf = AssetBlindingFactor::from_slice(&asset_bf[..32])?; + // Use `MissingRangeproof` error because it's available so does not require + // API breaks. In a later PR we should extend that enum and add #[non_exhaustive] + // to it. The maybe-better `MalformedAssetId` error requires we start with a + // std `FromSliceError` which we don't have. + let asset_and_bf = SliceExt::split_first_chunk::<64>(opening.message.as_ref()) + .ok_or(UnblindError::MissingRangeproof)? + .0; + let (asset_id, asset_bf) = asset_and_bf.split_array(); + + let asset_id = AssetId::from_byte_array(*asset_id); + let asset_bf = AssetBlindingFactor::from_byte_array(*asset_bf)?; + if let Asset::Confidential(own_asset) = self.asset { + let secp = Secp256k1::signing_only(); // needed to avoid API break + let asset = Generator::new_blinded(&secp, asset_id.into_tag(), asset_bf.into_inner()); + if asset != own_asset { + // See above about use of MissingRangeproof. + return Err(UnblindError::MissingRangeproof); + } + } let value = opening.value; let value_bf = ValueBlindingFactor(opening.blinding_factor); Ok(TxOutSecrets { - asset, + asset: asset_id, asset_bf, value, value_bf, diff --git a/src/confidential.rs b/src/confidential.rs index 34f1a564..5475e8a7 100644 --- a/src/confidential.rs +++ b/src/confidential.rs @@ -730,6 +730,11 @@ impl AssetBlindingFactor { AssetBlindingFactor(Tweak::new(rng)) } + /// Create from bytes. + pub fn from_byte_array(bytes: [u8; 32]) -> Result { + Ok(AssetBlindingFactor(Tweak::from_inner(bytes)?)) + } + /// Create from bytes. pub fn from_slice(bytes: &[u8]) -> Result { Ok(AssetBlindingFactor(Tweak::from_slice(bytes)?)) diff --git a/src/internals/array.rs b/src/internals/array.rs new file mode 100644 index 00000000..42fd1884 --- /dev/null +++ b/src/internals/array.rs @@ -0,0 +1,107 @@ +//! Contains extensions related to arrays. + +use std::convert::TryInto; + +/// Extension trait for arrays. +pub trait ArrayExt { + /// The item type the array is storing. + type Item; + + /// Just like the slicing operation, this returns an array `LEN` items long at position + /// `OFFSET`. + /// + /// The correctness of this operation is compile-time checked. + /// + /// Note that unlike slicing where the second number is the end index, here the second number + /// is array length! + fn sub_array(&self) -> &[Self::Item; LEN]; + + /// Returns an item at given statically-known index. + /// + /// This is just like normal indexing except the check happens at compile time. + fn get_static(&self) -> &Self::Item { &self.sub_array::()[0] } + + /// Returns the first item in an array. + /// + /// Fails to compile if the array is empty. + /// + /// Note that this method's name intentionally shadows the `std`'s `first` method which + /// returns `Option`. The rationale is that given the known length of the array, we always know + /// that this will not return `None` so trying to keep the `std` method around is pointless. + /// Importing the trait will also cause compile failures - that's also intentional to expose + /// the places where useless checks are made. + fn first(&self) -> &Self::Item { self.get_static::<0>() } + + /// Splits the array into two, non-overlapping smaller arrays covering the entire range. + /// + /// This is almost equivalent to just calling [`sub_array`](Self::sub_array) twice, except it also + /// checks that the arrays don't overlap and that they cover the full range. This is very useful + /// for demonstrating correctness, especially when chained. Using this technique even revealed + /// a bug in the past. ([#4195](https://github.com/rust-bitcoin/rust-bitcoin/issues/4195)) + fn split_array( + &self, + ) -> (&[Self::Item; LEFT], &[Self::Item; RIGHT]); + + /// Splits the array into the first element and the remaining, one element shorter, array. + /// + /// Fails to compile if the array is empty. + /// + /// Note that this method's name intentionally shadows the `std`'s `split_first` method which + /// returns `Option`. The rationale is that given the known length of the array, we always know + /// that this will not return `None` so trying to keep the `std` method around is pointless. + /// Importing the trait will also cause compile failures - that's also intentional to expose + /// the places where useless checks are made. + fn split_first(&self) -> (&Self::Item, &[Self::Item; RIGHT]) { + let (first, remaining) = self.split_array::<1, RIGHT>(); + (&first[0], remaining) + } + + /// Splits the array into the last element and the remaining, one element shorter, array. + /// + /// Fails to compile if the array is empty. + /// + /// Note that this method's name intentionally shadows the `std`'s `split_last` method which + /// returns `Option`. The rationale is that given the known length of the array, we always know + /// that this will not return `None` so trying to keep the `std` method around is pointless. + /// Importing the trait will also cause compile failures - that's also intentional to expose + /// the places where useless checks are made. + /// + /// The returned tuple is also reversed just as `std` for consistency and simpler diffs when + /// migrating. + fn split_last(&self) -> (&Self::Item, &[Self::Item; LEFT]) { + let (remaining, last) = self.split_array::(); + (&last[0], remaining) + } +} + +impl ArrayExt for [T; N] { + type Item = T; + + fn sub_array(&self) -> &[Self::Item; LEN] { + #[allow(clippy::let_unit_value)] + let () = Hack::::IS_VALID_RANGE; + + self[OFFSET..(OFFSET + LEN)].try_into().expect("this is also compiler-checked above") + } + + fn split_array( + &self, + ) -> (&[Self::Item; LEFT], &[Self::Item; RIGHT]) { + #[allow(clippy::let_unit_value)] + let () = Hack2::::IS_FULL_RANGE; + + (self.sub_array::<0, LEFT>(), self.sub_array::()) + } +} + +struct Hack; + +impl Hack { + const IS_VALID_RANGE: () = assert!(OFFSET + LEN <= N); +} + +struct Hack2; + +impl Hack2 { + const IS_FULL_RANGE: () = assert!(LEFT + RIGHT == N); +} diff --git a/src/internals/mod.rs b/src/internals/mod.rs new file mode 100644 index 00000000..443274a3 --- /dev/null +++ b/src/internals/mod.rs @@ -0,0 +1,5 @@ +// SPDX-License-Identifier: CC0-1.0 + +//! These modules are copied from the bitcoin-internals 0.5 crate from rust-bitcoin +pub mod array; +pub mod slice; \ No newline at end of file diff --git a/src/internals/slice.rs b/src/internals/slice.rs new file mode 100644 index 00000000..87fa6934 --- /dev/null +++ b/src/internals/slice.rs @@ -0,0 +1,188 @@ +//! Contains extensions related to slices. + +use std::convert::TryInto; + +/// Extension trait for slice. +pub trait SliceExt { + /// The item type the slice is storing. + type Item; + + /// Splits up the slice into a slice of arrays and a remainder. + /// + /// Note that `N` must not be zero: + /// + /// ```ignore + /// # use bitcoin_internals::slice::SliceExt; + /// let slice = [1, 2, 3]; + /// let _fail = slice.bitcoin_as_chunks::<0>(); // Fails to compile + /// ``` + fn bitcoin_as_chunks(&self) -> (&[[Self::Item; N]], &[Self::Item]); + + /// Splits up the slice into a slice of arrays and a remainder. + /// + /// Note that `N` must not be zero: + /// + /// ```ignore + /// # use bitcoin_internals::slice::SliceExt; + /// let mut slice = [1, 2, 3]; + /// let _fail = slice.bitcoin_as_chunks_mut::<0>(); // Fails to compile + /// ``` + fn bitcoin_as_chunks_mut( + &mut self, + ) -> (&mut [[Self::Item; N]], &mut [Self::Item]); + + /// Tries to access a sub-array of length `ARRAY_LEN` at the specified `offset`. + /// + /// Returns `None` in case of out-of-bounds access. + fn get_array(&self, offset: usize) -> Option<&[Self::Item; ARRAY_LEN]>; + + /// Splits the slice into an array and remainder if it's long enough. + /// + /// Returns `None` if the slice is shorter than `ARRAY_LEN` + #[allow(clippy::type_complexity)] // it's not really complex and redefining would make it + // harder to understand + fn split_first_chunk( + &self, + ) -> Option<(&[Self::Item; ARRAY_LEN], &[Self::Item])>; + + /// Splits the slice into a remainder and an array if it's long enough. + /// + /// Returns `None` if the slice is shorter than `ARRAY_LEN` + #[allow(clippy::type_complexity)] // it's not really complex and redefining would make it + // harder to understand + fn split_last_chunk( + &self, + ) -> Option<(&[Self::Item], &[Self::Item; ARRAY_LEN])>; +} + +impl SliceExt for [T] { + type Item = T; + + fn bitcoin_as_chunks(&self) -> (&[[Self::Item; N]], &[Self::Item]) { + #[allow(clippy::let_unit_value)] + let () = Hack::::IS_NONZERO; + + let chunks_count = self.len() / N; + let total_left_len = chunks_count * N; + let (left, right) = self.split_at(total_left_len); + // SAFETY: we've obtained the pointer from a slice that's still live + // we're merely casting, so no aliasing issues here + // arrays of T have same alignment as T + // the resulting slice points within the obtained slice as was computed above + let left = unsafe { + core::slice::from_raw_parts(left.as_ptr().cast::<[Self::Item; N]>(), chunks_count) + }; + (left, right) + } + + fn bitcoin_as_chunks_mut( + &mut self, + ) -> (&mut [[Self::Item; N]], &mut [Self::Item]) { + #[allow(clippy::let_unit_value)] + let () = Hack::::IS_NONZERO; + + let chunks_count = self.len() / N; + let total_left_len = chunks_count * N; + let (left, right) = self.split_at_mut(total_left_len); + // SAFETY: we've obtained the pointer from a slice that's still live + // we're merely casting, so no aliasing issues here + // arrays of T have same alignment as T + // the resulting slice points within the obtained slice as was computed above + let left = unsafe { + core::slice::from_raw_parts_mut( + left.as_mut_ptr().cast::<[Self::Item; N]>(), + chunks_count, + ) + }; + (left, right) + } + + fn get_array(&self, offset: usize) -> Option<&[Self::Item; ARRAY_LEN]> { + self.get(offset..(offset + ARRAY_LEN)).map(|slice| { + slice + .try_into() + .expect("the arguments to `get` evaluate to the same length the return type uses") + }) + } + + fn split_first_chunk( + &self, + ) -> Option<(&[Self::Item; ARRAY_LEN], &[Self::Item])> { + if self.len() < ARRAY_LEN { + return None; + } + let (first, remainder) = self.split_at(ARRAY_LEN); + Some((first.try_into().expect("we're passing `ARRAY_LEN` to `split_at` above"), remainder)) + } + + fn split_last_chunk( + &self, + ) -> Option<(&[Self::Item], &[Self::Item; ARRAY_LEN])> { + if self.len() < ARRAY_LEN { + return None; + } + let (remainder, last) = self.split_at(self.len() - ARRAY_LEN); + Some(( + remainder, + last.try_into().expect("we're passing `self.len() - ARRAY_LEN` to `split_at` above"), + )) + } +} + +struct Hack; + +impl Hack { + const IS_NONZERO: () = { + assert!(N != 0); + }; +} + +#[cfg(test)] +mod tests { + use super::SliceExt; + + // some comparisons require type annotations + const EMPTY: &[i32] = &[]; + + #[test] + fn one_to_one() { + let slice = [1]; + let (left, right) = slice.bitcoin_as_chunks::<1>(); + assert_eq!(left, &[[1]]); + assert_eq!(right, EMPTY); + } + + #[test] + fn one_to_two() { + const EMPTY_LEFT: &[[i32; 2]] = &[]; + + let slice = [1i32]; + let (left, right) = slice.bitcoin_as_chunks::<2>(); + assert_eq!(left, EMPTY_LEFT); + assert_eq!(right, &[1]); + } + + #[test] + fn two_to_one() { + let slice = [1, 2]; + let (left, right) = slice.bitcoin_as_chunks::<1>(); + assert_eq!(left, &[[1], [2]]); + assert_eq!(right, EMPTY); + } + + #[test] + fn two_to_two() { + let slice = [1, 2]; + let (left, right) = slice.bitcoin_as_chunks::<2>(); + assert_eq!(left, &[[1, 2]]); + assert_eq!(right, EMPTY); + } + + #[test] + fn three_to_two() { + let slice = [1, 2, 3]; + let (left, right) = slice.bitcoin_as_chunks::<2>(); + assert_eq!(left, &[[1, 2]]); + assert_eq!(right, &[3]); + } +} diff --git a/src/issuance.rs b/src/issuance.rs index d6387617..38b95c42 100644 --- a/src/issuance.rs +++ b/src/issuance.rs @@ -74,6 +74,11 @@ impl AssetId { 0x28, 0x29, 0xc0, 0xd0, 0x57, 0x9f, 0x0a, 0x71, 0x3d, 0x1c, 0x04, 0xed, 0xe9, 0x79, 0x02, 0x6f, ])); + + /// Constructs this wrapper struct from raw bytes. + pub fn from_byte_array(inner: [u8; 32]) -> Self { + Self(sha256::Midstate::from_byte_array(inner)) + } /// Create an [`AssetId`] from its inner type. pub const fn from_inner(midstate: sha256::Midstate) -> AssetId { diff --git a/src/lib.rs b/src/lib.rs index f90057ef..ea2da3aa 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -72,6 +72,9 @@ pub mod taproot; mod transaction; // consider making upstream public mod endian; +#[allow(dead_code)] +mod internals; + // re-export bitcoin deps which we re-use pub use bitcoin::hashes; // export everything at the top level so it can be used as `elements::Transaction` etc. diff --git a/src/pset/serialize.rs b/src/pset/serialize.rs index ed8af56c..4702f8c8 100644 --- a/src/pset/serialize.rs +++ b/src/pset/serialize.rs @@ -17,7 +17,6 @@ //! Defines traits used for (de)serializing PSET values into/from raw //! bytes in PSET key-value pairs. -use std::convert::TryFrom; use std::io; use crate::confidential::{self, AssetBlindingFactor}; @@ -30,6 +29,7 @@ use crate::{AssetId, BlockHash, Script, Transaction, TxOut, Txid}; use bitcoin; use bitcoin::bip32::{ChildNumber, Fingerprint, KeySource}; use bitcoin::{key::XOnlyPublicKey, PublicKey}; +use crate::internals::slice::SliceExt; use secp256k1_zkp::{self, RangeProof, SurjectionProof, Tweak}; use super::map::{PsbtSighashType, TapTree}; @@ -176,20 +176,17 @@ impl Serialize for KeySource { impl Deserialize for KeySource { fn deserialize(bytes: &[u8]) -> Result { - let prefix = match <[u8; 4]>::try_from(&bytes[0..4]) { - Ok(prefix) => prefix, - Err(_) => return Err(io::Error::from(io::ErrorKind::UnexpectedEof).into()), + let (prefix, mut rest) = match SliceExt::split_first_chunk::<4>(bytes) { + Some(v) => v, + None => return Err(io::Error::from(io::ErrorKind::UnexpectedEof).into()), }; let fprint: Fingerprint = Fingerprint::from(prefix); let mut dpath: Vec = Vec::default(); - let mut d = &bytes[4..]; - while !d.is_empty() { - match u32::consensus_decode(&mut d) { - Ok(index) => dpath.push(index.into()), - Err(e) => return Err(e), - } + while !rest.is_empty() { + let index = u32::consensus_decode(&mut rest)?; + dpath.push(index.into()); } Ok((fprint, dpath.into())) diff --git a/src/transaction.rs b/src/transaction.rs index 5af11ae6..323dd8d7 100644 --- a/src/transaction.rs +++ b/src/transaction.rs @@ -21,6 +21,8 @@ use std::convert::TryFrom; use bitcoin::{self, VarInt}; use crate::hashes::{Hash, sha256}; +use crate::internals::slice::SliceExt; + use crate::{confidential, ContractHash}; use crate::encode::{self, Encodable, Decodable}; @@ -433,12 +435,14 @@ impl<'tx> PeginData<'tx> { pegin_witness: &'tx [Vec], prevout: bitcoin::OutPoint, ) -> Result, &'static str> { - if pegin_witness.len() != 6 { - return Err("size not 6"); - } - if pegin_witness[5].len() < 80 { - return Err("merkle proof too short"); - } + let pegin_witness = match <&[Vec; 6]>::try_from(pegin_witness) { + Ok(v) => v, + Err(_) => return Err("size not 6"), + }; + let (block_header, _) = match SliceExt::split_first_chunk::<80>(pegin_witness[5].as_slice()) { + Some(v) => v, + None => return Err("merkle proof too short"), + }; Ok(PeginData { outpoint: prevout, @@ -449,7 +453,7 @@ impl<'tx> PeginData<'tx> { claim_script: &pegin_witness[3], tx: &pegin_witness[4], merkle_proof: &pegin_witness[5], - referenced_block: bitcoin::BlockHash::hash(&pegin_witness[5][0..80]), + referenced_block: bitcoin::BlockHash::hash(block_header), }) }