bitcoin_bosd/
serde.rs

1//! Custom serialization and deserialization for [`Descriptor`] using
2//! [`serde`](https://serde.rs).
3
4use hex::{DisplayHex, FromHex};
5use serde::{de::Error, Deserialize, Deserializer, Serialize, Serializer};
6
7use crate::Descriptor;
8
9impl Serialize for Descriptor {
10    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
11    where
12        S: Serializer,
13    {
14        // Convert the descriptor to bytes
15        let bytes = self.to_bytes();
16        if serializer.is_human_readable() {
17            // For human-readable formats, convert to hex string
18            bytes.to_lower_hex_string().serialize(serializer)
19        } else {
20            // For non-human-readable formats, use bytes directly
21            bytes.serialize(serializer)
22        }
23    }
24}
25
26impl<'de> Deserialize<'de> for Descriptor {
27    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
28    where
29        D: Deserializer<'de>,
30    {
31        if deserializer.is_human_readable() {
32            // For human-readable formats, expect hex string
33            let hex_str = String::deserialize(deserializer)?;
34            let bytes = Vec::from_hex(&hex_str).map_err(Error::custom)?;
35            if bytes.is_empty() {
36                return Err(Error::custom("empty input"));
37            }
38            Descriptor::from_vec(bytes).map_err(Error::custom)
39        } else {
40            // For non-human-readable formats, expect bytes directly
41            let bytes = Vec::<u8>::deserialize(deserializer)?;
42            if bytes.is_empty() {
43                return Err(Error::custom("empty input"));
44            }
45            Descriptor::from_vec(bytes).map_err(Error::custom)
46        }
47    }
48}
49
50#[cfg(test)]
51mod tests {
52    use super::*;
53    use crate::DescriptorType;
54    use std::str::FromStr;
55
56    #[cfg(test)]
57    mod proptest_tests {
58        use super::*;
59        use crate::descriptor::MAX_OP_RETURN_LEN;
60        use proptest::prelude::*;
61
62        proptest! {
63            /// Test that `OP_RETURN` descriptors serialize/deserialize correctly.
64            #[test]
65            fn op_return_serialization_roundtrip_property(data in prop::collection::vec(any::<u8>(), 0..=MAX_OP_RETURN_LEN)) {
66                if data.len() <= MAX_OP_RETURN_LEN {
67                    let mut bytes = vec![0u8; data.len() + 1];
68                    bytes[0] = 0; // OP_RETURN type tag
69                    bytes[1..].copy_from_slice(&data);
70
71                    let descriptor = Descriptor::from_bytes(&bytes).expect("valid OP_RETURN should parse");
72
73                    // Test JSON serialization
74                    let json_serialized = serde_json::to_string(&descriptor).unwrap();
75                    let json_deserialized: Descriptor = serde_json::from_str(&json_serialized).unwrap();
76                    assert_eq!(descriptor, json_deserialized);
77
78                    // Test bincode serialization
79                    let bincode_serialized = bincode::serialize(&descriptor).unwrap();
80                    let bincode_deserialized: Descriptor = bincode::deserialize(&bincode_serialized).unwrap();
81                    assert_eq!(descriptor, bincode_deserialized);
82                }
83            }
84        }
85    }
86
87    /// Helper function to test both JSON and bincode serialization.
88    fn test_roundtrip(descriptor: &Descriptor) {
89        // JSON (human-readable) serialization
90        let json_serialized = serde_json::to_string(&descriptor).unwrap();
91        let json_deserialized: Descriptor = serde_json::from_str(&json_serialized).unwrap();
92        assert_eq!(*descriptor, json_deserialized);
93
94        // Bincode (non-human-readable) serialization
95        let bincode_serialized = bincode::serialize(&descriptor).unwrap();
96        let bincode_deserialized: Descriptor = bincode::deserialize(&bincode_serialized).unwrap();
97        assert_eq!(*descriptor, bincode_deserialized);
98
99        // Verify different serialization formats produce different results
100        let json_bytes = serde_json::to_vec(&descriptor).unwrap();
101        assert_ne!(bincode_serialized, json_bytes);
102    }
103
104    #[test]
105    fn invalid_deserialization() {
106        // Test invalid JSON (hex string)
107        let invalid_json = "\"0500000000000000000000000000000000000000000000000000000000000000\"";
108        let json_result: Result<Descriptor, _> = serde_json::from_str(invalid_json);
109        assert!(json_result.is_err());
110
111        // Test invalid bincode (raw bytes)
112        let invalid_bytes: Vec<u8> = vec![5; 33]; // invalid type tag
113        let bincode_result: Result<Descriptor, _> = bincode::deserialize(&invalid_bytes);
114        assert!(bincode_result.is_err());
115
116        // Test empty input
117        let empty_json_result: Result<Descriptor, _> = serde_json::from_str("\"\"");
118        let empty_bincode_result: Result<Descriptor, _> = bincode::deserialize::<Descriptor>(&[]);
119        assert!(empty_json_result.is_err());
120        assert!(empty_bincode_result.is_err());
121    }
122
123    #[test]
124    fn serde_op_return() {
125        // OP_RETURN in hex string replacing the 6a (`OP_RETURN`)
126        // for a 0x00 (type_tag) byte for `OP_RETURN`.
127        // Source: https://bitcoin.stackexchange.com/a/29555
128        //         and transaction 8bae12b5f4c088d940733dcd1455efc6a3a69cf9340e17a981286d3778615684
129        let descriptor = Descriptor::from_str("00636861726c6579206c6f766573206865696469").unwrap();
130
131        test_roundtrip(&descriptor);
132        assert_eq!(descriptor.type_tag(), DescriptorType::OpReturn);
133        assert_eq!(descriptor.payload(), b"charley loves heidi");
134    }
135
136    #[test]
137    fn serde_p2pkh() {
138        // P2PKH
139        // Using 0x01 (type_tag) and a 20-byte hash
140        // Source: transaction 8bae12b5f4c088d940733dcd1455efc6a3a69cf9340e17a981286d3778615684
141        // Corresponds to address `1HnhWpkMHMjgt167kvgcPyurMmsCQ2WPgg`
142        let descriptor =
143            Descriptor::from_str("01b8268ce4d481413c4e848ff353cd16104291c45b").unwrap();
144
145        test_roundtrip(&descriptor);
146        assert_eq!(descriptor.type_tag(), DescriptorType::P2pkh);
147    }
148
149    #[test]
150    fn serde_p2sh() {
151        // P2SH
152        // Using 0x02 (type_tag) and a 20-byte hash
153        // Source: transaction a0f1aaa2fb4582c89e0511df0374a5a2833bf95f7314f4a51b55b7b71e90ce0f
154        // Corresponds to address `3CK4fEwbMP7heJarmU4eqA3sMbVJyEnU3V`
155        let descriptor =
156            Descriptor::from_str("02748284390f9e263a4b766a75d0633c50426eb875").unwrap();
157
158        test_roundtrip(&descriptor);
159        assert_eq!(descriptor.type_tag(), DescriptorType::P2sh);
160    }
161
162    #[test]
163    fn serde_p2wpkh() {
164        // Using 0x03 (type_tag) and a 20-byte hash
165        // Source: transaction 7c53ba0f1fc65f021749cac6a9c163e499fcb2e539b08c040802be55c33d32fe
166        // Corresponds to address `bc1qvugyzunmnq5y8alrmdrxnsh4gts9p9hmvhyd40`
167        let descriptor =
168            Descriptor::from_str("03671041727b982843f7e3db4669c2f542e05096fb").unwrap();
169
170        test_roundtrip(&descriptor);
171        assert_eq!(descriptor.type_tag(), DescriptorType::P2wpkh);
172    }
173
174    #[test]
175    fn serde_p2wsh() {
176        // P2WSH
177        // Using 0x03 (type_tag) and a 32-byte hash
178        // Source: transaction fbf3517516ebdf03358a9ef8eb3569f96ac561c162524e37e9088eb13b228849
179        // Corresponds to address `bc1qvhu3557twysq2ldn6dut6rmaj3qk04p60h9l79wk4lzgy0ca8mfsnffz65`
180        let descriptor = Descriptor::from_str(
181            "0365f91a53cb7120057db3d378bd0f7d944167d43a7dcbff15d6afc4823f1d3ed3",
182        )
183        .unwrap();
184
185        test_roundtrip(&descriptor);
186        assert_eq!(descriptor.type_tag(), DescriptorType::P2wsh);
187    }
188
189    #[test]
190    fn serde_p2a() {
191        // P2A
192        // Using 0x04 (type_tag) and a 0-byte payload
193        // Source: transaction c054743f0f3ecfac2cf08c40c7dd36fcb38928cf8e07d179693ca2692d041848
194        // Corresponds to address `bc1pfeessrawgf`
195        let descriptor = Descriptor::from_str("04").unwrap();
196
197        test_roundtrip(&descriptor);
198        assert_eq!(descriptor.type_tag(), DescriptorType::P2a);
199    }
200
201    #[test]
202    fn serde_p2tr() {
203        // P2TR
204        // Using 0x04 (type_tag) and a 32-byte hash
205        // Source: transaction a7115c7267dbb4aab62b37818d431b784fe731f4d2f9fa0939a9980d581690ec
206        // Corresponds to address `bc1ppuxgmd6n4j73wdp688p08a8rte97dkn5n70r2ym6kgsw0v3c5ensrytduf`
207        let descriptor = Descriptor::from_str(
208            "040f0c8db753acbd17343a39c2f3f4e35e4be6da749f9e35137ab220e7b238a667",
209        )
210        .unwrap();
211
212        test_roundtrip(&descriptor);
213        assert_eq!(descriptor.type_tag(), DescriptorType::P2tr);
214    }
215
216    #[test]
217    fn compare_serialization_formats() {
218        // P2TR
219        // Using 0x04 (type_tag) and a 32-byte hash
220        // Source: transaction a7115c7267dbb4aab62b37818d431b784fe731f4d2f9fa0939a9980d581690ec
221        // Corresponds to address `bc1ppuxgmd6n4j73wdp688p08a8rte97dkn5n70r2ym6kgsw0v3c5ensrytduf`
222        let descriptor = Descriptor::from_str(
223            "040f0c8db753acbd17343a39c2f3f4e35e4be6da749f9e35137ab220e7b238a667",
224        )
225        .unwrap();
226
227        // Get serialized data in different formats
228        let json_bytes = serde_json::to_vec(&descriptor).unwrap();
229        let bincode_bytes = bincode::serialize(&descriptor).unwrap();
230
231        // Verify they're different
232        assert_ne!(json_bytes, bincode_bytes);
233
234        // Verify both can be deserialized correctly
235        let json_deserialized: Descriptor = serde_json::from_slice(&json_bytes).unwrap();
236        let bincode_deserialized: Descriptor = bincode::deserialize(&bincode_bytes).unwrap();
237
238        assert_eq!(descriptor, json_deserialized);
239        assert_eq!(descriptor, bincode_deserialized);
240    }
241
242    #[test]
243    fn test_human_readable_format() {
244        // P2TR
245        // Using 0x04 (type_tag) and a 32-byte hash
246        // Source: transaction a7115c7267dbb4aab62b37818d431b784fe731f4d2f9fa0939a9980d581690ec
247        // Corresponds to address `bc1ppuxgmd6n4j73wdp688p08a8rte97dkn5n70r2ym6kgsw0v3c5ensrytduf`
248        let descriptor = Descriptor::from_str(
249            "040f0c8db753acbd17343a39c2f3f4e35e4be6da749f9e35137ab220e7b238a667",
250        )
251        .unwrap();
252
253        // Serialize to string (human-readable)
254        let json_string = serde_json::to_string(&descriptor).unwrap();
255
256        // Verify it's actually readable (contains hex characters)
257        assert!(json_string
258            .chars()
259            .all(|c| c.is_ascii_hexdigit() || c == '"'));
260
261        // Deserialize and verify
262        let deserialized: Descriptor = serde_json::from_str(&json_string).unwrap();
263        assert_eq!(descriptor, deserialized);
264    }
265}