bitcoin_bosd/
serde.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
//! Custom serialization and deserialization for [`Descriptor`] using
//! [`serde`](https://serde.rs).

use hex::{DisplayHex, FromHex};
use serde::{de::Error, Deserialize, Deserializer, Serialize, Serializer};

use crate::Descriptor;

impl Serialize for Descriptor {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        // Convert the descriptor to bytes
        let bytes = self.to_bytes();
        if serializer.is_human_readable() {
            // For human-readable formats, convert to hex string
            bytes.to_lower_hex_string().serialize(serializer)
        } else {
            // For non-human-readable formats, use bytes directly
            bytes.serialize(serializer)
        }
    }
}

impl<'de> Deserialize<'de> for Descriptor {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        if deserializer.is_human_readable() {
            // For human-readable formats, expect hex string
            let hex_str = String::deserialize(deserializer)?;
            let bytes = Vec::from_hex(&hex_str).map_err(Error::custom)?;
            if bytes.is_empty() {
                return Err(Error::custom("empty input"));
            }
            Descriptor::from_vec(bytes).map_err(Error::custom)
        } else {
            // For non-human-readable formats, expect bytes directly
            let bytes = Vec::<u8>::deserialize(deserializer)?;
            if bytes.is_empty() {
                return Err(Error::custom("empty input"));
            }
            Descriptor::from_vec(bytes).map_err(Error::custom)
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::DescriptorType;
    use std::str::FromStr;

    /// Helper function to test both JSON and bincode serialization.
    fn test_roundtrip(descriptor: &Descriptor) {
        // JSON (human-readable) serialization
        let json_serialized = serde_json::to_string(&descriptor).unwrap();
        let json_deserialized: Descriptor = serde_json::from_str(&json_serialized).unwrap();
        assert_eq!(*descriptor, json_deserialized);

        // Bincode (non-human-readable) serialization
        let bincode_serialized = bincode::serialize(&descriptor).unwrap();
        let bincode_deserialized: Descriptor = bincode::deserialize(&bincode_serialized).unwrap();
        assert_eq!(*descriptor, bincode_deserialized);

        // Verify different serialization formats produce different results
        let json_bytes = serde_json::to_vec(&descriptor).unwrap();
        assert_ne!(bincode_serialized, json_bytes);
    }

    #[test]
    fn invalid_deserialization() {
        // Test invalid JSON (hex string)
        let invalid_json = "\"0500000000000000000000000000000000000000000000000000000000000000\"";
        let json_result: Result<Descriptor, _> = serde_json::from_str(invalid_json);
        assert!(json_result.is_err());

        // Test invalid bincode (raw bytes)
        let invalid_bytes: Vec<u8> = vec![5; 33]; // invalid type tag
        let bincode_result: Result<Descriptor, _> = bincode::deserialize(&invalid_bytes);
        assert!(bincode_result.is_err());

        // Test empty input
        let empty_json_result: Result<Descriptor, _> = serde_json::from_str("\"\"");
        let empty_bincode_result: Result<Descriptor, _> = bincode::deserialize::<Descriptor>(&[]);
        assert!(empty_json_result.is_err());
        assert!(empty_bincode_result.is_err());
    }

    #[test]
    fn serde_op_return() {
        // OP_RETURN in hex string replacing the 6a (`OP_RETURN`)
        // for a 0x00 (type_tag) byte for `OP_RETURN`.
        // Source: https://bitcoin.stackexchange.com/a/29555
        //         and transaction 8bae12b5f4c088d940733dcd1455efc6a3a69cf9340e17a981286d3778615684
        let descriptor = Descriptor::from_str("00636861726c6579206c6f766573206865696469").unwrap();

        test_roundtrip(&descriptor);
        assert_eq!(descriptor.type_tag(), DescriptorType::OpReturn);
        assert_eq!(descriptor.payload(), b"charley loves heidi");
    }

    #[test]
    fn serde_p2pkh() {
        // P2PKH
        // Using 0x01 (type_tag) and a 20-byte hash
        // Source: transaction 8bae12b5f4c088d940733dcd1455efc6a3a69cf9340e17a981286d3778615684
        // Corresponds to address `1HnhWpkMHMjgt167kvgcPyurMmsCQ2WPgg`
        let descriptor =
            Descriptor::from_str("01b8268ce4d481413c4e848ff353cd16104291c45b").unwrap();

        test_roundtrip(&descriptor);
        assert_eq!(descriptor.type_tag(), DescriptorType::P2pkh);
    }

    #[test]
    fn serde_p2sh() {
        // P2SH
        // Using 0x02 (type_tag) and a 20-byte hash
        // Source: transaction a0f1aaa2fb4582c89e0511df0374a5a2833bf95f7314f4a51b55b7b71e90ce0f
        // Corresponds to address `3CK4fEwbMP7heJarmU4eqA3sMbVJyEnU3V`
        let descriptor =
            Descriptor::from_str("02748284390f9e263a4b766a75d0633c50426eb875").unwrap();

        test_roundtrip(&descriptor);
        assert_eq!(descriptor.type_tag(), DescriptorType::P2sh);
    }

    #[test]
    fn serde_p2wpkh() {
        // Using 0x03 (type_tag) and a 20-byte hash
        // Source: transaction 7c53ba0f1fc65f021749cac6a9c163e499fcb2e539b08c040802be55c33d32fe
        // Corresponds to address `bc1qvugyzunmnq5y8alrmdrxnsh4gts9p9hmvhyd40`
        let descriptor =
            Descriptor::from_str("03671041727b982843f7e3db4669c2f542e05096fb").unwrap();

        test_roundtrip(&descriptor);
        assert_eq!(descriptor.type_tag(), DescriptorType::P2wpkh);
    }

    #[test]
    fn serde_p2wsh() {
        // P2WSH
        // Using 0x03 (type_tag) and a 32-byte hash
        // Source: transaction fbf3517516ebdf03358a9ef8eb3569f96ac561c162524e37e9088eb13b228849
        // Corresponds to address `bc1qvhu3557twysq2ldn6dut6rmaj3qk04p60h9l79wk4lzgy0ca8mfsnffz65`
        let descriptor = Descriptor::from_str(
            "0365f91a53cb7120057db3d378bd0f7d944167d43a7dcbff15d6afc4823f1d3ed3",
        )
        .unwrap();

        test_roundtrip(&descriptor);
        assert_eq!(descriptor.type_tag(), DescriptorType::P2wsh);
    }

    #[test]
    fn serde_p2tr() {
        // P2TR
        // Using 0x04 (type_tag) and a 32-byte hash
        // Source: transaction a7115c7267dbb4aab62b37818d431b784fe731f4d2f9fa0939a9980d581690ec
        // Corresponds to address `bc1ppuxgmd6n4j73wdp688p08a8rte97dkn5n70r2ym6kgsw0v3c5ensrytduf`
        let descriptor = Descriptor::from_str(
            "040f0c8db753acbd17343a39c2f3f4e35e4be6da749f9e35137ab220e7b238a667",
        )
        .unwrap();

        test_roundtrip(&descriptor);
        assert_eq!(descriptor.type_tag(), DescriptorType::P2tr);
    }

    #[test]
    fn compare_serialization_formats() {
        // P2TR
        // Using 0x04 (type_tag) and a 32-byte hash
        // Source: transaction a7115c7267dbb4aab62b37818d431b784fe731f4d2f9fa0939a9980d581690ec
        // Corresponds to address `bc1ppuxgmd6n4j73wdp688p08a8rte97dkn5n70r2ym6kgsw0v3c5ensrytduf`
        let descriptor = Descriptor::from_str(
            "040f0c8db753acbd17343a39c2f3f4e35e4be6da749f9e35137ab220e7b238a667",
        )
        .unwrap();

        // Get serialized data in different formats
        let json_bytes = serde_json::to_vec(&descriptor).unwrap();
        let bincode_bytes = bincode::serialize(&descriptor).unwrap();

        // Verify they're different
        assert_ne!(json_bytes, bincode_bytes);

        // Verify both can be deserialized correctly
        let json_deserialized: Descriptor = serde_json::from_slice(&json_bytes).unwrap();
        let bincode_deserialized: Descriptor = bincode::deserialize(&bincode_bytes).unwrap();

        assert_eq!(descriptor, json_deserialized);
        assert_eq!(descriptor, bincode_deserialized);
    }

    #[test]
    fn test_human_readable_format() {
        // P2TR
        // Using 0x04 (type_tag) and a 32-byte hash
        // Source: transaction a7115c7267dbb4aab62b37818d431b784fe731f4d2f9fa0939a9980d581690ec
        // Corresponds to address `bc1ppuxgmd6n4j73wdp688p08a8rte97dkn5n70r2ym6kgsw0v3c5ensrytduf`
        let descriptor = Descriptor::from_str(
            "040f0c8db753acbd17343a39c2f3f4e35e4be6da749f9e35137ab220e7b238a667",
        )
        .unwrap();

        // Serialize to string (human-readable)
        let json_string = serde_json::to_string(&descriptor).unwrap();

        // Verify it's actually readable (contains hex characters)
        assert!(json_string
            .chars()
            .all(|c| c.is_ascii_hexdigit() || c == '"'));

        // Deserialize and verify
        let deserialized: Descriptor = serde_json::from_str(&json_string).unwrap();
        assert_eq!(descriptor, deserialized);
    }
}