From 45a6938f2b78c01aa8cd91ada035dc0b2b6397d2 Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Wed, 27 Aug 2025 14:32:47 +0000 Subject: [PATCH 1/3] export pset::error::PsetHash This type appears in the public API as part of the pset::Error type. However, the type itself is not exported, preventing users from naming or directly constructing it. This prevents construction of a single variant of pset::Error, and in particular prevents us from semver-tricking the Error type. It is bad practice to export the pset::Error type without also exporting its constituent types (except as an intentional form of sealing, which this is not). That is enough reason to accept this PR. But to give a more elaborate reason: 1. I would like to semver-trick 0.25.3 by re-exporting all the types from 0.26.0 except the ones that changed (`PartiallySignedTransaction`, `TxOut`, `pset::raw::ProprietaryKey`, and types that expose these in their public APIs: `Transaction`, `Block` and the sighash cache). 2. I need to re-export the `encode::Error` type, which fortunately did not change since 0.25.x, so that I can re-use the Encodable/Decodable logic for all the types that I'm re-exporting. (I can either re-export those traits directly, along with the error type, or implement a new one with impls that pass through to the 0.26.x one ... or retain a *ton* of explicit unreviewable code which probably won't even compile without changes to handle some types becoming foreign). 3. However, `encode::Error` has conversions to and from `pset::Error`. So if I re-export `encode::Error` I need to re-export `pset::Error`. (Also, I just really want to; everything I *don't* re-export is a bunch of extra explicit code that needs to stick around in this version of the library. See above.) 4. But I need to explicitly construct `pset::Error` in the `Map::insert_pair` impl for `pset::Output`. (This *can't* be re-exported because it's private, and I can't re-export the whole `pset::Output` type because it has a method which returns `TxOut`, whose API changed in 0.26.0.) 5. ...so I need a re-export of `PsetHash` to do the construction. --- src/pset/error.rs | 4 ++++ src/pset/mod.rs | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/pset/error.rs b/src/pset/error.rs index 0cce0c61..1f6d4564 100644 --- a/src/pset/error.rs +++ b/src/pset/error.rs @@ -26,9 +26,13 @@ use secp256k1_zkp; #[derive(Copy, Clone, PartialEq, Eq, Debug)] /// Enum for marking pset hash error pub enum PsetHash { + /// Bad preimage for `RIPEMD160` hash. Ripemd, + /// Bad preimage for `SHA256` hash. Sha256, + /// Bad preimage for `RIPEMD160-SHA256` hash. Hash160, + /// Bad preimage for double-`SHA256` hash. Hash256, } /// Ways that a Partially Signed Transaction might fail. diff --git a/src/pset/mod.rs b/src/pset/mod.rs index 34106095..85ee99b6 100644 --- a/src/pset/mod.rs +++ b/src/pset/mod.rs @@ -53,7 +53,7 @@ use crate::{ use secp256k1_zkp::rand::{CryptoRng, RngCore}; use secp256k1_zkp::{self, RangeProof, SecretKey, SurjectionProof}; -pub use self::error::{Error, PsetBlindError}; +pub use self::error::{Error, PsetBlindError, PsetHash}; use self::map::Map; pub use self::map::{Global, GlobalTxData, Input, Output, PsbtSighashType, TapTree}; From 2b89d5f847e52031854e88acda7a6598a156eb1c Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Wed, 27 Aug 2025 18:46:44 +0000 Subject: [PATCH 2/3] encode: implement Encodable and Decodable for Vec Right now we have explicit impls of Vec for a ton of specific types T. This is annoying for users, who are unable to implement Vec themselves for their own types. In particular is super annoying for me trying to do a semver trick with elements 0.25 and 0.26, since I can't reexport the 0.26 Encodable trait since I would need to implement it for Vec for the 0.25 TxOut type. (But conversely, if I retain the full trait definition in 0.25, I can't blanket-impl this trait for all T: Encodable because of this Vec crap; so I wind up needing to explicitly implement Encodable and Decodeable for every single type I re-export, and if I forget any then that's a semver violation. Brutal.) We do this is because we need a specialized impl for Vec and Rust has no specialization. However, Kixunil pointed out on rust-bitcoin that we can just do "runtime specialization" by checking TypeId::of:: and doing the efficient thing when T is u8. In practice, because the compiler knows T at codegen time, it will know how the check goes and simply delete the check and the untaken branch, so we have exactly the same result as we would if we had specialization. This works only for T: 'static, but it turns out that all of our types that we currently impl Encodable for Vec on are 'static (and it's hard to imagine one that wouldn't be, since all bitcoin/elements primitive types are simple PODs). It also seems plausible that a user could construct a Vec which the compiler somehow doesn't realize is a Vec (e.g. if they had a Vec or something, in some future Rust version that supports that). Such code will be sound and correct, but be slow because it won't use the fast path. But I don't think this is a practical concern because I can't even figure out how to do it.. --- src/encode.rs | 142 ++++++++++++++++++++++++++------------------------ 1 file changed, 73 insertions(+), 69 deletions(-) diff --git a/src/encode.rs b/src/encode.rs index 6868cee2..e8718c36 100644 --- a/src/encode.rs +++ b/src/encode.rs @@ -16,14 +16,13 @@ //! use std::io::Cursor; -use std::{error, fmt, io, mem}; +use std::{any, error, fmt, io, mem}; use bitcoin::ScriptBuf; use secp256k1_zkp::{self, RangeProof, SurjectionProof, Tweak}; use crate::hashes::{sha256, Hash}; use crate::pset; -use crate::transaction::{Transaction, TxIn, TxOut}; pub use bitcoin::{self, consensus::encode::MAX_VEC_SIZE}; @@ -314,47 +313,84 @@ impl Decodable for bitcoin::hashes::sha256d::Hash { } // Vectors -macro_rules! impl_vec { - ($type: ty) => { - impl Encodable for Vec<$type> { - #[inline] - fn consensus_encode(&self, mut s: S) -> Result { - let mut len = 0; - len += VarInt(self.len() as u64).consensus_encode(&mut s)?; - for c in self.iter() { - len += c.consensus_encode(&mut s)?; - } - Ok(len) +impl Encodable for [T] { + #[inline] + fn consensus_encode(&self, mut s: S) -> Result { + if any::TypeId::of::() == any::TypeId::of::() { + // SAFETY: checked that T is exactly u8, so &self, of type, &[T], is exactly &[u8] + let u8_slice = unsafe { + std::slice::from_raw_parts(self.as_ptr().cast::(), self.len()) + }; + consensus_encode_with_size(u8_slice, s) + } else { + let mut len = 0; + len += VarInt(self.len() as u64).consensus_encode(&mut s)?; + for c in self { + len += c.consensus_encode(&mut s)?; } + Ok(len) } + } +} - impl Decodable for Vec<$type> { - #[inline] - fn consensus_decode(mut d: D) -> Result { - let len = VarInt::consensus_decode(&mut d)?.0; - let byte_size = (len as usize) - .checked_mul(mem::size_of::<$type>()) - .ok_or(self::Error::ParseFailed("Invalid length"))?; - if byte_size > MAX_VEC_SIZE { - return Err(self::Error::OversizedVectorAllocation { - requested: byte_size, - max: MAX_VEC_SIZE, - }); - } - let mut ret = Vec::with_capacity(len as usize); - for _ in 0..len { - ret.push(Decodable::consensus_decode(&mut d)?); - } - Ok(ret) +impl Encodable for Vec { + #[inline] + fn consensus_encode(&self, s: S) -> Result { + self[..].consensus_encode(s) + } +} + +impl Encodable for Box<[T]> { + #[inline] + fn consensus_encode(&self, s: S) -> Result { + self[..].consensus_encode(s) + } +} + +impl Decodable for Vec { + #[inline] + fn consensus_decode(mut d: D) -> Result { + if any::TypeId::of::() == any::TypeId::of::() { + let s = VarInt::consensus_decode(&mut d)?.0 as usize; + if s > MAX_VEC_SIZE { + return Err(self::Error::OversizedVectorAllocation { + requested: s, + max: MAX_VEC_SIZE, + }); + } + let mut v = vec![0; s]; + d.read_slice(&mut v)?; + // SAFETY: checked that T is exactly u8, so v, of type, Vec, is exactly Vec + unsafe { + Ok(std::mem::transmute::, Vec>(v)) + } + } else { + let len = VarInt::consensus_decode(&mut d)?.0; + let byte_size = (len as usize) + .checked_mul(mem::size_of::()) + .ok_or(self::Error::ParseFailed("Invalid length"))?; + if byte_size > MAX_VEC_SIZE { + return Err(self::Error::OversizedVectorAllocation { + requested: byte_size, + max: MAX_VEC_SIZE, + }); } + let mut ret = Vec::with_capacity(len as usize); + for _ in 0..len { + ret.push(Decodable::consensus_decode(&mut d)?); + } + Ok(ret) } - }; + } +} + +impl Decodable for Box<[T]> { + #[inline] + fn consensus_decode(d: D) -> Result { + let v = Vec::::consensus_decode(d)?; + Ok(v.into()) + } } -impl_vec!(TxIn); -impl_vec!(TxOut); -impl_vec!(Transaction); -impl_vec!(TapLeafHash); -impl_vec!(Vec); // Vec> macro_rules! impl_array { ( $size:literal ) => { @@ -383,38 +419,6 @@ impl_array!(4); impl_array!(32); impl_array!(33); -impl Encodable for Box<[u8]> { - fn consensus_encode(&self, mut w: W) -> Result { - consensus_encode_with_size(&self[..], &mut w) - } -} -impl Decodable for Box<[u8]> { - fn consensus_decode(d: D) -> Result { - let v = Vec::::consensus_decode(d)?; - Ok(v.into()) - } -} - -impl Encodable for Vec { - fn consensus_encode(&self, mut w: W) -> Result { - consensus_encode_with_size(&self[..], &mut w) - } -} -impl Decodable for Vec { - fn consensus_decode(mut d: D) -> Result { - let s = VarInt::consensus_decode(&mut d)?.0 as usize; - if s > MAX_VEC_SIZE { - return Err(self::Error::OversizedVectorAllocation { - requested: s, - max: MAX_VEC_SIZE, - }); - } - let mut v = vec![0; s]; - d.read_slice(&mut v)?; - Ok(v) - } -} - macro_rules! impl_box_option { ($type: ty) => { impl Encodable for Option> { From 05a24ad3715e8ba0649a2c756c906ff40e8b6f5c Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Wed, 27 Aug 2025 14:32:47 +0000 Subject: [PATCH 3/3] bump version to 0.26.1 Let's just cut a release with these changes. Then I can do a followup PR which uses the semver trick in a new 0.25.3. --- CHANGELOG.md | 6 ++++++ Cargo-latest.lock | 2 +- Cargo.toml | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 70f719f0..179c2d2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,10 @@ +# 0.26.1 - 2025-08-28 + +* [#250](https://github.com/ElementsProject/rust-elements/pull/250) API cleanups + * implement `Encodable` and `Decodable` for `Vec` whenever `T` is `Encodable`/`Decodable` and `'static` + * add missing export of error sub-type `pset::PsetHash` + # 0.26.0 - 2025-08-22 * [#249](https://github.com/ElementsProject/rust-elements/pull/249) docs: fix changelog links diff --git a/Cargo-latest.lock b/Cargo-latest.lock index 5f9503a1..426e4104 100644 --- a/Cargo-latest.lock +++ b/Cargo-latest.lock @@ -190,7 +190,7 @@ checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "elements" -version = "0.26.0" +version = "0.26.1" dependencies = [ "bech32", "bincode", diff --git a/Cargo.toml b/Cargo.toml index 31614b4e..5b5f627f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "elements" -version = "0.26.0" +version = "0.26.1" 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"