BREAKING: Change Psbt serde implementations

Replace derived Psbt serde implementation with one that conforms to
BIP-174. In human readable serde contexts, serialize to the base64
encoded format, and in binary serde contexts, serialize to the raw
binary format.

The previous derived serde implementation cannot be used in a backward or
forward compatible way in binary formats like bincode, which means that
every field added to the Psbt struct would break serde de/serialization
into binary formats. Instead, this one-time breaking change will fix the
issue going forward.

Downstream users with persisted data in the old serde format should continue
using 0.32.x to create migrations to the new format.
This commit is contained in:
Daniel Roberts 2025-05-11 10:28:19 -05:00
parent d7e9a84339
commit 9aa235c24d
13 changed files with 58 additions and 352 deletions

View File

@ -4,6 +4,10 @@
- Use MAX_MONEY in serde regression test [#3950](https://github.com/rust-bitcoin/rust-bitcoin/pull/3950) - Use MAX_MONEY in serde regression test [#3950](https://github.com/rust-bitcoin/rust-bitcoin/pull/3950)
## Breaking changes
- Change Psbt serde implementation to contextually use the PSBT binary or base64 encoded formats described in BIP-174.
# 0.33.0-alpha.0 - 2024-11-18 # 0.33.0-alpha.0 - 2024-11-18
This series of alpha releases is meant for two things: This series of alpha releases is meant for two things:

View File

@ -19,7 +19,7 @@ default = [ "std", "secp-recovery" ]
std = ["base58/std", "bech32/std", "hashes/std", "hex/std", "internals/std", "io/std", "primitives/std", "secp256k1/std", "units/std", "base64?/std", "bitcoinconsensus?/std"] std = ["base58/std", "bech32/std", "hashes/std", "hex/std", "internals/std", "io/std", "primitives/std", "secp256k1/std", "units/std", "base64?/std", "bitcoinconsensus?/std"]
rand-std = ["secp256k1/rand", "std"] rand-std = ["secp256k1/rand", "std"]
rand = ["secp256k1/rand"] rand = ["secp256k1/rand"]
serde = ["dep:serde", "hashes/serde", "internals/serde", "primitives/serde", "secp256k1/serde", "units/serde"] serde = ["base64", "dep:serde", "hashes/serde", "internals/serde", "primitives/serde", "secp256k1/serde", "units/serde"]
secp-lowmemory = ["secp256k1/lowmemory"] secp-lowmemory = ["secp256k1/lowmemory"]
secp-recovery = ["secp256k1/recovery"] secp-recovery = ["secp256k1/recovery"]
arbitrary = ["dep:arbitrary", "units/arbitrary", "primitives/arbitrary"] arbitrary = ["dep:arbitrary", "units/arbitrary", "primitives/arbitrary"]

View File

@ -92,8 +92,6 @@ pub extern crate secp256k1;
extern crate serde; extern crate serde;
mod internal_macros; mod internal_macros;
#[cfg(feature = "serde")]
mod serde_utils;
#[macro_use] #[macro_use]
pub mod p2p; pub mod p2p;

View File

@ -65,7 +65,6 @@ const PSBT_IN_PROPRIETARY: u64 = 0xFC;
/// A key-value map for an input of the corresponding index in the unsigned /// A key-value map for an input of the corresponding index in the unsigned
/// transaction. /// transaction.
#[derive(Clone, Default, Debug, PartialEq, Eq, Hash)] #[derive(Clone, Default, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Input { pub struct Input {
/// The non-witness transaction this input spends from. Should only be /// The non-witness transaction this input spends from. Should only be
/// `Option::Some` for inputs which spend non-SegWit outputs or /// `Option::Some` for inputs which spend non-SegWit outputs or
@ -87,7 +86,6 @@ pub struct Input {
pub witness_script: Option<ScriptBuf>, pub witness_script: Option<ScriptBuf>,
/// A map from public keys needed to sign this input to their corresponding /// A map from public keys needed to sign this input to their corresponding
/// master key fingerprints and derivation paths. /// master key fingerprints and derivation paths.
#[cfg_attr(feature = "serde", serde(with = "crate::serde_utils::btreemap_as_seq"))]
pub bip32_derivation: BTreeMap<secp256k1::PublicKey, KeySource>, pub bip32_derivation: BTreeMap<secp256k1::PublicKey, KeySource>,
/// The finalized, fully-constructed scriptSig with signatures and any other /// The finalized, fully-constructed scriptSig with signatures and any other
/// scripts necessary for this input to pass validation. /// scripts necessary for this input to pass validation.
@ -96,37 +94,28 @@ pub struct Input {
/// other scripts necessary for this input to pass validation. /// other scripts necessary for this input to pass validation.
pub final_script_witness: Option<Witness>, pub final_script_witness: Option<Witness>,
/// RIPEMD160 hash to preimage map. /// RIPEMD160 hash to preimage map.
#[cfg_attr(feature = "serde", serde(with = "crate::serde_utils::btreemap_byte_values"))]
pub ripemd160_preimages: BTreeMap<ripemd160::Hash, Vec<u8>>, pub ripemd160_preimages: BTreeMap<ripemd160::Hash, Vec<u8>>,
/// SHA256 hash to preimage map. /// SHA256 hash to preimage map.
#[cfg_attr(feature = "serde", serde(with = "crate::serde_utils::btreemap_byte_values"))]
pub sha256_preimages: BTreeMap<sha256::Hash, Vec<u8>>, pub sha256_preimages: BTreeMap<sha256::Hash, Vec<u8>>,
/// HASH160 hash to preimage map. /// HASH160 hash to preimage map.
#[cfg_attr(feature = "serde", serde(with = "crate::serde_utils::btreemap_byte_values"))]
pub hash160_preimages: BTreeMap<hash160::Hash, Vec<u8>>, pub hash160_preimages: BTreeMap<hash160::Hash, Vec<u8>>,
/// HASH256 hash to preimage map. /// HASH256 hash to preimage map.
#[cfg_attr(feature = "serde", serde(with = "crate::serde_utils::btreemap_byte_values"))]
pub hash256_preimages: BTreeMap<sha256d::Hash, Vec<u8>>, pub hash256_preimages: BTreeMap<sha256d::Hash, Vec<u8>>,
/// Serialized Taproot signature with sighash type for key spend. /// Serialized Taproot signature with sighash type for key spend.
pub tap_key_sig: Option<taproot::Signature>, pub tap_key_sig: Option<taproot::Signature>,
/// Map of `<xonlypubkey>|<leafhash>` with signature. /// Map of `<xonlypubkey>|<leafhash>` with signature.
#[cfg_attr(feature = "serde", serde(with = "crate::serde_utils::btreemap_as_seq"))]
pub tap_script_sigs: BTreeMap<(XOnlyPublicKey, TapLeafHash), taproot::Signature>, pub tap_script_sigs: BTreeMap<(XOnlyPublicKey, TapLeafHash), taproot::Signature>,
/// Map of Control blocks to Script version pair. /// Map of Control blocks to Script version pair.
#[cfg_attr(feature = "serde", serde(with = "crate::serde_utils::btreemap_as_seq"))]
pub tap_scripts: BTreeMap<ControlBlock, (ScriptBuf, LeafVersion)>, pub tap_scripts: BTreeMap<ControlBlock, (ScriptBuf, LeafVersion)>,
/// Map of tap root x only keys to origin info and leaf hashes contained in it. /// Map of tap root x only keys to origin info and leaf hashes contained in it.
#[cfg_attr(feature = "serde", serde(with = "crate::serde_utils::btreemap_as_seq"))]
pub tap_key_origins: BTreeMap<XOnlyPublicKey, (Vec<TapLeafHash>, KeySource)>, pub tap_key_origins: BTreeMap<XOnlyPublicKey, (Vec<TapLeafHash>, KeySource)>,
/// Taproot Internal key. /// Taproot Internal key.
pub tap_internal_key: Option<XOnlyPublicKey>, pub tap_internal_key: Option<XOnlyPublicKey>,
/// Taproot Merkle root. /// Taproot Merkle root.
pub tap_merkle_root: Option<TapNodeHash>, pub tap_merkle_root: Option<TapNodeHash>,
/// Proprietary key-value pairs for this input. /// Proprietary key-value pairs for this input.
#[cfg_attr(feature = "serde", serde(with = "crate::serde_utils::btreemap_as_seq_byte_values"))]
pub proprietary: BTreeMap<raw::ProprietaryKey, Vec<u8>>, pub proprietary: BTreeMap<raw::ProprietaryKey, Vec<u8>>,
/// Unknown key-value pairs for this input. /// Unknown key-value pairs for this input.
#[cfg_attr(feature = "serde", serde(with = "crate::serde_utils::btreemap_as_seq_byte_values"))]
pub unknown: BTreeMap<raw::Key, Vec<u8>>, pub unknown: BTreeMap<raw::Key, Vec<u8>>,
} }
@ -147,7 +136,6 @@ pub struct Input {
/// let _tap_sighash_all: PsbtSighashType = TapSighashType::All.into(); /// let _tap_sighash_all: PsbtSighashType = TapSighashType::All.into();
/// ``` /// ```
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct PsbtSighashType { pub struct PsbtSighashType {
pub(in crate::psbt) inner: u32, pub(in crate::psbt) inner: u32,
} }

View File

@ -26,7 +26,6 @@ const PSBT_OUT_PROPRIETARY: u64 = 0xFC;
/// A key-value map for an output of the corresponding index in the unsigned /// A key-value map for an output of the corresponding index in the unsigned
/// transaction. /// transaction.
#[derive(Clone, Default, Debug, PartialEq, Eq, Hash)] #[derive(Clone, Default, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Output { pub struct Output {
/// The redeem script for this output. /// The redeem script for this output.
pub redeem_script: Option<ScriptBuf>, pub redeem_script: Option<ScriptBuf>,
@ -34,20 +33,16 @@ pub struct Output {
pub witness_script: Option<ScriptBuf>, pub witness_script: Option<ScriptBuf>,
/// A map from public keys needed to spend this output to their /// A map from public keys needed to spend this output to their
/// corresponding master key fingerprints and derivation paths. /// corresponding master key fingerprints and derivation paths.
#[cfg_attr(feature = "serde", serde(with = "crate::serde_utils::btreemap_as_seq"))]
pub bip32_derivation: BTreeMap<secp256k1::PublicKey, KeySource>, pub bip32_derivation: BTreeMap<secp256k1::PublicKey, KeySource>,
/// The internal pubkey. /// The internal pubkey.
pub tap_internal_key: Option<XOnlyPublicKey>, pub tap_internal_key: Option<XOnlyPublicKey>,
/// Taproot Output tree. /// Taproot Output tree.
pub tap_tree: Option<TapTree>, pub tap_tree: Option<TapTree>,
/// Map of tap root x only keys to origin info and leaf hashes contained in it. /// Map of tap root x only keys to origin info and leaf hashes contained in it.
#[cfg_attr(feature = "serde", serde(with = "crate::serde_utils::btreemap_as_seq"))]
pub tap_key_origins: BTreeMap<XOnlyPublicKey, (Vec<TapLeafHash>, KeySource)>, pub tap_key_origins: BTreeMap<XOnlyPublicKey, (Vec<TapLeafHash>, KeySource)>,
/// Proprietary key-value pairs for this output. /// Proprietary key-value pairs for this output.
#[cfg_attr(feature = "serde", serde(with = "crate::serde_utils::btreemap_as_seq_byte_values"))]
pub proprietary: BTreeMap<raw::ProprietaryKey, Vec<u8>>, pub proprietary: BTreeMap<raw::ProprietaryKey, Vec<u8>>,
/// Unknown key-value pairs for this output. /// Unknown key-value pairs for this output.
#[cfg_attr(feature = "serde", serde(with = "crate::serde_utils::btreemap_as_seq_byte_values"))]
pub unknown: BTreeMap<raw::Key, Vec<u8>>, pub unknown: BTreeMap<raw::Key, Vec<u8>>,
} }

View File

@ -40,7 +40,6 @@ pub use self::{
/// A Partially Signed Transaction. /// A Partially Signed Transaction.
#[derive(Debug, Clone, PartialEq, Eq, Hash)] #[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Psbt { pub struct Psbt {
/// The unsigned transaction, scriptSigs and witnesses for each input must be empty. /// The unsigned transaction, scriptSigs and witnesses for each input must be empty.
pub unsigned_tx: Transaction, pub unsigned_tx: Transaction,
@ -50,10 +49,8 @@ pub struct Psbt {
/// derivation path as defined by BIP 32. /// derivation path as defined by BIP 32.
pub xpub: BTreeMap<Xpub, KeySource>, pub xpub: BTreeMap<Xpub, KeySource>,
/// Global proprietary key-value pairs. /// Global proprietary key-value pairs.
#[cfg_attr(feature = "serde", serde(with = "crate::serde_utils::btreemap_as_seq_byte_values"))]
pub proprietary: BTreeMap<raw::ProprietaryKey, Vec<u8>>, pub proprietary: BTreeMap<raw::ProprietaryKey, Vec<u8>>,
/// Unknown global key-value pairs. /// Unknown global key-value pairs.
#[cfg_attr(feature = "serde", serde(with = "crate::serde_utils::btreemap_as_seq_byte_values"))]
pub unknown: BTreeMap<raw::Key, Vec<u8>>, pub unknown: BTreeMap<raw::Key, Vec<u8>>,
/// The corresponding key-value map for each input in the unsigned transaction. /// The corresponding key-value map for each input in the unsigned transaction.
@ -731,6 +728,53 @@ impl Psbt {
} }
} }
#[cfg(feature = "serde")]
impl serde::Serialize for Psbt {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
use crate::prelude::ToString;
if serializer.is_human_readable() {
serializer.serialize_str(&self.to_string())
} else {
serializer.serialize_bytes(&self.serialize())
}
}
}
#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for Psbt {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>
{
struct Visitor;
impl<'de> serde::de::Visitor<'de> for Visitor
{
type Value = Psbt;
fn expecting(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
write!(f, "a psbt")
}
fn visit_bytes<E: serde::de::Error>(self, bytes: &[u8]) -> Result<Self::Value, E> {
Psbt::deserialize(bytes)
.map_err(|e| serde::de::Error::custom(e))
}
fn visit_str<E: serde::de::Error>(self, s: &str) -> Result<Self::Value, E> {
s.parse().map_err(|e| serde::de::Error::custom(e))
}
}
if deserializer.is_human_readable() {
deserializer.deserialize_str(Visitor)
} else {
deserializer.deserialize_bytes(Visitor)
}
}
}
/// Data required to call [`GetKey`] to get the private key to sign an input. /// Data required to call [`GetKey`] to get the private key to sign an input.
#[derive(Clone, Debug, PartialEq, Eq)] #[derive(Clone, Debug, PartialEq, Eq)]
#[non_exhaustive] #[non_exhaustive]

View File

@ -21,25 +21,21 @@ use crate::psbt::Error;
/// ///
/// `<key> := <keylen> <keytype> <keydata>` /// `<key> := <keylen> <keytype> <keydata>`
#[derive(Debug, PartialEq, Hash, Eq, Clone, Ord, PartialOrd)] #[derive(Debug, PartialEq, Hash, Eq, Clone, Ord, PartialOrd)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Key { pub struct Key {
/// The type of this PSBT key. /// The type of this PSBT key.
pub type_value: u64, // Encoded as a compact size. pub type_value: u64, // Encoded as a compact size.
/// The key data itself in raw byte form. /// The key data itself in raw byte form.
#[cfg_attr(feature = "serde", serde(with = "crate::serde_utils::hex_bytes"))]
pub key_data: Vec<u8>, pub key_data: Vec<u8>,
} }
/// A PSBT key-value pair in its raw byte form. /// A PSBT key-value pair in its raw byte form.
/// `<keypair> := <key> <value>` /// `<keypair> := <key> <value>`
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Pair { pub struct Pair {
/// The key of this key-value pair. /// The key of this key-value pair.
pub key: Key, pub key: Key,
/// The value data of this key-value pair in raw byte form. /// The value data of this key-value pair in raw byte form.
/// `<value> := <valuelen> <valuedata>` /// `<value> := <valuelen> <valuedata>`
#[cfg_attr(feature = "serde", serde(with = "crate::serde_utils::hex_bytes"))]
pub value: Vec<u8>, pub value: Vec<u8>,
} }
@ -49,19 +45,16 @@ pub type ProprietaryType = u64;
/// Proprietary keys (i.e. keys starting with 0xFC byte) with their internal /// Proprietary keys (i.e. keys starting with 0xFC byte) with their internal
/// structure according to BIP 174. /// structure according to BIP 174.
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct ProprietaryKey<Subtype = ProprietaryType> pub struct ProprietaryKey<Subtype = ProprietaryType>
where where
Subtype: Copy + From<u64> + Into<u64>, Subtype: Copy + From<u64> + Into<u64>,
{ {
/// Proprietary type prefix used for grouping together keys under some /// Proprietary type prefix used for grouping together keys under some
/// application and avoid namespace collision /// application and avoid namespace collision
#[cfg_attr(feature = "serde", serde(with = "crate::serde_utils::hex_bytes"))]
pub prefix: Vec<u8>, pub prefix: Vec<u8>,
/// Custom proprietary subtype /// Custom proprietary subtype
pub subtype: Subtype, pub subtype: Subtype,
/// Additional key bytes (like serialized public key data etc) /// Additional key bytes (like serialized public key data etc)
#[cfg_attr(feature = "serde", serde(with = "crate::serde_utils::hex_bytes"))]
pub key: Vec<u8>, pub key: Vec<u8>,
} }

View File

@ -1,298 +0,0 @@
// SPDX-License-Identifier: CC0-1.0
//! Bitcoin serde utilities.
//!
//! This module is for special serde serializations.
pub(crate) struct SerializeBytesAsHex<'a>(pub(crate) &'a [u8]);
impl serde::Serialize for SerializeBytesAsHex<'_> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
use hex::DisplayHex;
serializer.collect_str(&format_args!("{:x}", self.0.as_hex()))
}
}
pub mod btreemap_byte_values {
//! Module for serialization of BTreeMaps with hex byte values.
#![allow(missing_docs)]
// NOTE: This module can be exactly copied to use with HashMap.
use hex::FromHex;
use crate::prelude::{BTreeMap, Vec};
pub fn serialize<S, T>(v: &BTreeMap<T, Vec<u8>>, s: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
T: serde::Serialize + core::hash::Hash + Eq + Ord,
{
use serde::ser::SerializeMap;
// Don't do anything special when not human readable.
if !s.is_human_readable() {
serde::Serialize::serialize(v, s)
} else {
let mut map = s.serialize_map(Some(v.len()))?;
for (key, value) in v.iter() {
map.serialize_entry(key, &super::SerializeBytesAsHex(value))?;
}
map.end()
}
}
pub fn deserialize<'de, D, T>(d: D) -> Result<BTreeMap<T, Vec<u8>>, D::Error>
where
D: serde::Deserializer<'de>,
T: serde::Deserialize<'de> + core::hash::Hash + Eq + Ord,
{
use core::marker::PhantomData;
struct Visitor<T>(PhantomData<T>);
impl<'de, T> serde::de::Visitor<'de> for Visitor<T>
where
T: serde::Deserialize<'de> + core::hash::Hash + Eq + Ord,
{
type Value = BTreeMap<T, Vec<u8>>;
fn expecting(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
write!(f, "a map with hexadecimal values")
}
fn visit_map<A: serde::de::MapAccess<'de>>(
self,
mut a: A,
) -> Result<Self::Value, A::Error> {
let mut ret = BTreeMap::new();
while let Some((key, value)) = a.next_entry()? {
ret.insert(key, FromHex::from_hex(value).map_err(serde::de::Error::custom)?);
}
Ok(ret)
}
}
// Don't do anything special when not human readable.
if !d.is_human_readable() {
serde::Deserialize::deserialize(d)
} else {
d.deserialize_map(Visitor(PhantomData))
}
}
}
pub mod btreemap_as_seq {
//! Module for serialization of BTreeMaps as lists of sequences because
//! serde_json will not serialize hashmaps with non-string keys be default.
#![allow(missing_docs)]
// NOTE: This module can be exactly copied to use with HashMap.
use crate::prelude::BTreeMap;
pub fn serialize<S, T, U>(v: &BTreeMap<T, U>, s: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
T: serde::Serialize + core::hash::Hash + Eq + Ord,
U: serde::Serialize,
{
use serde::ser::SerializeSeq;
// Don't do anything special when not human readable.
if !s.is_human_readable() {
serde::Serialize::serialize(v, s)
} else {
let mut seq = s.serialize_seq(Some(v.len()))?;
for pair in v.iter() {
seq.serialize_element(&pair)?;
}
seq.end()
}
}
pub fn deserialize<'de, D, T, U>(d: D) -> Result<BTreeMap<T, U>, D::Error>
where
D: serde::Deserializer<'de>,
T: serde::Deserialize<'de> + core::hash::Hash + Eq + Ord,
U: serde::Deserialize<'de>,
{
use core::marker::PhantomData;
struct Visitor<T, U>(PhantomData<(T, U)>);
impl<'de, T, U> serde::de::Visitor<'de> for Visitor<T, U>
where
T: serde::Deserialize<'de> + core::hash::Hash + Eq + Ord,
U: serde::Deserialize<'de>,
{
type Value = BTreeMap<T, U>;
fn expecting(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
write!(f, "a sequence of pairs")
}
fn visit_seq<A: serde::de::SeqAccess<'de>>(
self,
mut a: A,
) -> Result<Self::Value, A::Error> {
let mut ret = BTreeMap::new();
while let Some((key, value)) = a.next_element()? {
ret.insert(key, value);
}
Ok(ret)
}
}
// Don't do anything special when not human readable.
if !d.is_human_readable() {
serde::Deserialize::deserialize(d)
} else {
d.deserialize_seq(Visitor(PhantomData))
}
}
}
pub mod btreemap_as_seq_byte_values {
//! Module for serialization of BTreeMaps as lists of sequences because
//! serde_json will not serialize hashmaps with non-string keys be default.
#![allow(missing_docs)]
// NOTE: This module can be exactly copied to use with HashMap.
use crate::prelude::{BTreeMap, Vec};
/// A custom key-value pair type that serialized the bytes as hex.
#[derive(Debug, Deserialize)]
struct OwnedPair<T>(
T,
#[serde(deserialize_with = "crate::serde_utils::hex_bytes::deserialize")] Vec<u8>,
);
/// A custom key-value pair type that serialized the bytes as hex.
#[derive(Debug, Serialize)]
struct BorrowedPair<'a, T: 'static>(
&'a T,
#[serde(serialize_with = "crate::serde_utils::hex_bytes::serialize")] &'a [u8],
);
pub fn serialize<S, T>(v: &BTreeMap<T, Vec<u8>>, s: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
T: serde::Serialize + core::hash::Hash + Eq + Ord + 'static,
{
use serde::ser::SerializeSeq;
// Don't do anything special when not human readable.
if !s.is_human_readable() {
serde::Serialize::serialize(v, s)
} else {
let mut seq = s.serialize_seq(Some(v.len()))?;
for (key, value) in v.iter() {
seq.serialize_element(&BorrowedPair(key, value))?;
}
seq.end()
}
}
pub fn deserialize<'de, D, T>(d: D) -> Result<BTreeMap<T, Vec<u8>>, D::Error>
where
D: serde::Deserializer<'de>,
T: serde::Deserialize<'de> + core::hash::Hash + Eq + Ord,
{
use core::marker::PhantomData;
struct Visitor<T>(PhantomData<T>);
impl<'de, T> serde::de::Visitor<'de> for Visitor<T>
where
T: serde::Deserialize<'de> + core::hash::Hash + Eq + Ord,
{
type Value = BTreeMap<T, Vec<u8>>;
fn expecting(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
write!(f, "a sequence of pairs")
}
fn visit_seq<A: serde::de::SeqAccess<'de>>(
self,
mut a: A,
) -> Result<Self::Value, A::Error> {
let mut ret = BTreeMap::new();
while let Option::Some(OwnedPair(key, value)) = a.next_element()? {
ret.insert(key, value);
}
Ok(ret)
}
}
// Don't do anything special when not human readable.
if !d.is_human_readable() {
serde::Deserialize::deserialize(d)
} else {
d.deserialize_seq(Visitor(PhantomData))
}
}
}
pub mod hex_bytes {
//! Module for serialization of byte arrays as hex strings.
#![allow(missing_docs)]
use hex::FromHex;
pub fn serialize<T, S>(bytes: &T, s: S) -> Result<S::Ok, S::Error>
where
T: serde::Serialize + AsRef<[u8]>,
S: serde::Serializer,
{
// Don't do anything special when not human readable.
if !s.is_human_readable() {
serde::Serialize::serialize(bytes, s)
} else {
serde::Serialize::serialize(&super::SerializeBytesAsHex(bytes.as_ref()), s)
}
}
pub fn deserialize<'de, D, B>(d: D) -> Result<B, D::Error>
where
D: serde::Deserializer<'de>,
B: serde::Deserialize<'de> + FromHex,
{
struct Visitor<B>(core::marker::PhantomData<B>);
impl<B: FromHex> serde::de::Visitor<'_> for Visitor<B> {
type Value = B;
fn expecting(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result {
formatter.write_str("an ASCII hex string")
}
fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
if let Ok(hex) = core::str::from_utf8(v) {
FromHex::from_hex(hex).map_err(E::custom)
} else {
Err(E::invalid_value(serde::de::Unexpected::Bytes(v), &self))
}
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
FromHex::from_hex(v).map_err(E::custom)
}
}
// Don't do anything special when not human readable.
if !d.is_human_readable() {
serde::Deserialize::deserialize(d)
} else {
d.deserialize_str(Visitor(core::marker::PhantomData))
}
}
}

View File

@ -0,0 +1 @@
"cHNidP8BAFMBAAAAAYmjxx6rTSDgNxu7pMxpj6KVyUY6+i45f4UzzLYvlWflAQAAAAD/////AXL++E4sAAAAF6kUM5cluiHv1irHU6m80GfWx6ajnQWHAAAAAE8BBIiyHgAAAAAAAAAAAIc9/4HAL1JWI/0f5RZ+rDpVoEnePTFLtC7iJ//tN9UIAzmjYBMwFZfa70H75ZOgLMUT0LVVJ+wt8QUOLo/0nIXCDN6tvu8AAACAAQAAABD8BXByZWZ4KnRlc3Rfa2V5AwUGBwMJAAEDAwQFAAEAjwEAAAAAAQGJo8ceq00g4Dcbu6TMaY+ilclGOvouOX+FM8y2L5Vn5QEAAAAXFgAUvhjRUqmwEgOdrz2n3k9TNJ7suYX/////AXL++E4sAAAAF6kUM5cluiHv1irHU6m80GfWx6ajnQWHASED0uFWdJQbrUqZY3LLh+GFbTZSYG2YVi/jnF6efkE/IQUAAAAAAQEgcv74TiwAAAAXqRQzlyW6Ie/WKsdTqbzQZ9bHpqOdBYciAgM5iA3JI5S3NV49BDn6KDwx3nWQgS6gEcQkXAZ0poXog0cwRAIgT2fir7dhQtRPrliiSV0zo0GdqibNDbjQTzRStjKJrA8CIBB2Kp+2fpTMXK2QJvbcmf9/Bw9CeNMPvH0Mhp3TjH/nAQEDBIMAAAABBAFRIgYDOYgNySOUtzVePQQ5+ig8Md51kIEuoBHEJFwGdKaF6IMM3q2+7wAAAIABAAAAAQgGAgIBAwEFFQoYn3yLGjhv/o7tkbODDHp7zR53jAIBAiELoShx/uIQ+4YZKR6uoZRYHL0lMeSyN1nSJfaAaSP2MiICAQIVDBXMSeGRy8Ug2RlEYApct3r2qjKRAgECIQ12pWrO2RXSUT3NhMLDeLLoqlzWMrW3HKLyrFsOOmSb2wIBAhD8BXByZWZ4KnRlc3Rfa2V5AwUGBwMJAAEDAwQFACICAzmIDckjlLc1Xj0EOfooPDHedZCBLqARxCRcBnSmheiDDN6tvu8AAACAAQAAABD8BXByZWZ4KnRlc3Rfa2V5AwUGBwMJAAEDAwQFAA=="

View File

@ -30,7 +30,7 @@ use bitcoin::consensus::encode::deserialize;
use bitcoin::hashes::{hash160, ripemd160, sha256, sha256d}; use bitcoin::hashes::{hash160, ripemd160, sha256, sha256d};
use bitcoin::hex::FromHex; use bitcoin::hex::FromHex;
use bitcoin::locktime::{absolute, relative}; use bitcoin::locktime::{absolute, relative};
use bitcoin::psbt::raw::{self, Key, Pair, ProprietaryKey}; use bitcoin::psbt::raw;
use bitcoin::psbt::{Input, Output, Psbt, PsbtSighashType}; use bitcoin::psbt::{Input, Output, Psbt, PsbtSighashType};
use bitcoin::script::ScriptBufExt as _; use bitcoin::script::ScriptBufExt as _;
use bitcoin::sighash::{EcdsaSighashType, TapSighashType}; use bitcoin::sighash::{EcdsaSighashType, TapSighashType};
@ -320,30 +320,11 @@ fn serde_regression_psbt() {
let got = serialize(&psbt).unwrap(); let got = serialize(&psbt).unwrap();
let want = include_bytes!("data/serde/psbt_bincode") as &[_]; let want = include_bytes!("data/serde/psbt_bincode") as &[_];
assert_eq!(got, want) assert_eq!(got, want);
}
#[test] let got = serde_json::to_string(&psbt).unwrap();
fn serde_regression_raw_pair() { let want = include_str!("data/serde/psbt_base64.json");
let pair = Pair { assert_eq!(got, want);
key: Key { type_value: 1u64, key_data: vec![0u8, 1u8, 2u8, 3u8] },
value: vec![0u8, 1u8, 2u8, 3u8],
};
let got = serialize(&pair).unwrap();
let want = include_bytes!("data/serde/raw_pair_bincode") as &[_];
assert_eq!(got, want)
}
#[test]
fn serde_regression_proprietary_key() {
let key = ProprietaryKey {
prefix: vec![0u8, 1u8, 2u8, 3u8],
subtype: 1u64,
key: vec![0u8, 1u8, 2u8, 3u8],
};
let got = serialize(&key).unwrap();
let want = include_bytes!("data/serde/proprietary_key_bincode") as &[_];
assert_eq!(got, want)
} }
#[test] #[test]