Skip to main content

dpp/address_funds/
witness.rs

1#[cfg(feature = "json-conversion")]
2use crate::serialization::json_safe_fields;
3use bincode::de::BorrowDecoder;
4use bincode::enc::Encoder;
5use bincode::error::{DecodeError, EncodeError};
6use bincode::Encode;
7use platform_value::BinaryData;
8#[cfg(feature = "serde-conversion")]
9use serde::{Deserialize, Serialize};
10
11/// Maximum number of entries in a P2SH signatures vector.
12/// This is 16 (max keys from OP_PUSHNUM_16) + 1 (CHECKMULTISIG dummy byte).
13pub const MAX_P2SH_SIGNATURES: usize = 17;
14
15/// The input witness data required to spend from a PlatformAddress.
16///
17/// This enum captures the different spending patterns for P2PKH and P2SH addresses.
18///
19/// Wire shape (internally tagged on `type`, camelCase variants/fields):
20///   `{ "$type": "p2pkh", "signature": <BinaryData> }`
21///   `{ "$type": "p2sh", "signatures": [<BinaryData>, ...], "redeemScript": <BinaryData> }`
22///
23/// Note: `MAX_P2SH_SIGNATURES` is enforced by the bincode `Decode` path (the
24/// load-bearing wire format). The serde JSON/Value deserialize path does not
25/// enforce it; downstream consumers must validate signature counts before
26/// re-serializing for storage.
27#[cfg_attr(feature = "json-conversion", json_safe_fields)]
28#[derive(Debug, Clone, PartialEq, Ord, PartialOrd, Eq)]
29#[cfg_attr(
30    feature = "serde-conversion",
31    derive(Serialize, Deserialize),
32    serde(tag = "$type")
33)]
34pub enum AddressWitness {
35    /// P2PKH witness: recoverable signature only
36    ///
37    /// Used for spending from a Pay-to-Public-Key-Hash address.
38    /// The public key is recovered from the signature during verification,
39    /// saving 33 bytes per witness compared to including the public key.
40    #[cfg_attr(feature = "serde-conversion", serde(rename = "p2pkh"))]
41    P2pkh {
42        /// The recoverable ECDSA signature (65 bytes with recovery byte prefix)
43        signature: BinaryData, //todo change to [u8;65]
44    },
45    /// P2SH witness: signatures + redeem script
46    ///
47    /// Used for spending from a Pay-to-Script-Hash address (e.g., multisig).
48    /// For a 2-of-3 multisig, signatures would be `[OP_0, sig1, sig2]` and
49    /// redeem_script would be `OP_2 <pub1> <pub2> <pub3> OP_3 OP_CHECKMULTISIG`.
50    #[cfg_attr(feature = "serde-conversion", serde(rename = "p2sh"))]
51    P2sh {
52        /// The signatures (may include placeholder bytes like OP_0 for CHECKMULTISIG bug)
53        signatures: Vec<BinaryData>,
54        /// The redeem script that hashes to the address
55        #[cfg_attr(feature = "serde-conversion", serde(rename = "redeemScript"))]
56        redeem_script: BinaryData,
57    },
58}
59
60impl Encode for AddressWitness {
61    fn encode<E: Encoder>(&self, encoder: &mut E) -> Result<(), EncodeError> {
62        match self {
63            AddressWitness::P2pkh { signature } => {
64                0u8.encode(encoder)?;
65                signature.encode(encoder)?;
66            }
67            AddressWitness::P2sh {
68                signatures,
69                redeem_script,
70            } => {
71                1u8.encode(encoder)?;
72                signatures.encode(encoder)?;
73                redeem_script.encode(encoder)?;
74            }
75        }
76        Ok(())
77    }
78}
79
80// Share the wire schema and domain checks across both decoding APIs.
81macro_rules! impl_address_witness_decode {
82    ($decode:ident, $decoder:ident, $method:ident, $untrusted:expr) => {
83        impl<C> bincode::$decode<C> for AddressWitness {
84            fn $method<D: bincode::de::$decoder<Context = C>>(
85                decoder: &mut D,
86            ) -> Result<Self, DecodeError> {
87                let discriminant = u8::$method(decoder)?;
88                match discriminant {
89                    0 => {
90                        let signature = BinaryData::$method(decoder)?;
91                        Ok(AddressWitness::P2pkh { signature })
92                    }
93                    1 => {
94                        let signatures = Vec::<BinaryData>::$method(decoder)?;
95                        if signatures.len() > MAX_P2SH_SIGNATURES {
96                            return Err(DecodeError::OtherString(format!(
97                                "P2SH signatures count {} exceeds maximum {}",
98                                signatures.len(),
99                                MAX_P2SH_SIGNATURES,
100                            )));
101                        }
102                        let redeem_script = BinaryData::$method(decoder)?;
103                        Ok(AddressWitness::P2sh {
104                            signatures,
105                            redeem_script,
106                        })
107                    }
108                    _ => Err(DecodeError::OtherString(format!(
109                        "Invalid AddressWitness discriminant: {}",
110                        discriminant
111                    ))),
112                }
113            }
114        }
115    };
116}
117impl_address_witness_decode!(Decode, Decoder, decode, false);
118impl_address_witness_decode!(DecodeUntrusted, UntrustedDecoder, decode_untrusted, true);
119bincode::impl_borrow_decode_untrusted!(AddressWitness);
120
121impl<'de, C> bincode::BorrowDecode<'de, C> for AddressWitness {
122    fn borrow_decode<D: BorrowDecoder<'de, Context = C>>(
123        decoder: &mut D,
124    ) -> Result<Self, DecodeError> {
125        let discriminant = u8::borrow_decode(decoder)?;
126        match discriminant {
127            0 => {
128                let signature = BinaryData::borrow_decode(decoder)?;
129                Ok(AddressWitness::P2pkh { signature })
130            }
131            1 => {
132                let signatures = Vec::<BinaryData>::borrow_decode(decoder)?;
133                if signatures.len() > MAX_P2SH_SIGNATURES {
134                    return Err(DecodeError::OtherString(format!(
135                        "P2SH signatures count {} exceeds maximum {}",
136                        signatures.len(),
137                        MAX_P2SH_SIGNATURES,
138                    )));
139                }
140                let redeem_script = BinaryData::borrow_decode(decoder)?;
141                Ok(AddressWitness::P2sh {
142                    signatures,
143                    redeem_script,
144                })
145            }
146            _ => Err(DecodeError::OtherString(format!(
147                "Invalid AddressWitness discriminant: {}",
148                discriminant
149            ))),
150        }
151    }
152}
153
154impl AddressWitness {
155    /// Generates a unique identifier for this witness based on its contents.
156    ///
157    /// This is used for deduplication purposes in unique_identifiers() implementations.
158    pub fn unique_id(&self) -> String {
159        use base64::prelude::BASE64_STANDARD;
160        use base64::Engine;
161
162        let mut data = Vec::new();
163
164        match self {
165            AddressWitness::P2pkh { signature } => {
166                data.push(0u8);
167                data.extend_from_slice(signature.as_slice());
168            }
169            AddressWitness::P2sh {
170                signatures,
171                redeem_script,
172            } => {
173                data.push(1u8);
174                data.extend_from_slice(redeem_script.as_slice());
175                for sig in signatures {
176                    data.extend_from_slice(sig.as_slice());
177                }
178            }
179        }
180
181        BASE64_STANDARD.encode(&data)
182    }
183
184    /// Returns the redeem script if this is a P2SH witness
185    pub fn redeem_script(&self) -> Option<&BinaryData> {
186        match self {
187            AddressWitness::P2pkh { .. } => None,
188            AddressWitness::P2sh { redeem_script, .. } => Some(redeem_script),
189        }
190    }
191
192    /// Returns true if this is a P2PKH witness
193    pub fn is_p2pkh(&self) -> bool {
194        matches!(self, AddressWitness::P2pkh { .. })
195    }
196
197    /// Returns true if this is a P2SH witness
198    pub fn is_p2sh(&self) -> bool {
199        matches!(self, AddressWitness::P2sh { .. })
200    }
201}
202
203#[cfg(test)]
204#[allow(clippy::needless_borrows_for_generic_args)]
205mod tests {
206    use super::*;
207    use bincode::config;
208
209    #[test]
210    fn test_p2pkh_witness_encode_decode() {
211        let witness = AddressWitness::P2pkh {
212            signature: BinaryData::new(vec![0x30, 0x44, 0x02, 0x20]),
213        };
214
215        let encoded = bincode::encode_to_vec(&witness, config::standard()).unwrap();
216        let decoded: AddressWitness = bincode::decode_from_slice(&encoded, config::standard())
217            .unwrap()
218            .0;
219
220        assert_eq!(witness, decoded);
221        assert!(decoded.is_p2pkh());
222        assert!(!decoded.is_p2sh());
223    }
224
225    #[test]
226    fn test_p2sh_witness_encode_decode() {
227        let witness = AddressWitness::P2sh {
228            signatures: vec![
229                BinaryData::new(vec![0x00]),                   // OP_0 placeholder
230                BinaryData::new(vec![0x30, 0x44, 0x02, 0x20]), // sig1
231                BinaryData::new(vec![0x30, 0x45, 0x02, 0x21]), // sig2
232            ],
233            redeem_script: BinaryData::new(vec![
234                0x52, // OP_2
235                0x21, // push 33 bytes (pubkey1)
236                0x02, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12,
237                0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12,
238                0x12, 0x12, 0x12, 0x12, 0x12, 0x53, // OP_3
239                0xae, // OP_CHECKMULTISIG
240            ]),
241        };
242
243        let encoded = bincode::encode_to_vec(&witness, config::standard()).unwrap();
244        let decoded: AddressWitness = bincode::decode_from_slice(&encoded, config::standard())
245            .unwrap()
246            .0;
247
248        assert_eq!(witness, decoded);
249        assert!(!decoded.is_p2pkh());
250        assert!(decoded.is_p2sh());
251    }
252
253    #[test]
254    fn test_unique_id_p2pkh() {
255        let witness = AddressWitness::P2pkh {
256            signature: BinaryData::new(vec![0x30, 0x44]),
257        };
258
259        let id = witness.unique_id();
260        assert!(!id.is_empty());
261
262        // Different signature should produce different ID
263        let witness2 = AddressWitness::P2pkh {
264            signature: BinaryData::new(vec![0x30, 0x45]),
265        };
266        assert_ne!(id, witness2.unique_id());
267    }
268
269    #[test]
270    fn test_unique_id_p2sh() {
271        let witness = AddressWitness::P2sh {
272            signatures: vec![
273                BinaryData::new(vec![0x00]),
274                BinaryData::new(vec![0x30, 0x44]),
275            ],
276            redeem_script: BinaryData::new(vec![0x52, 0xae]),
277        };
278
279        let id = witness.unique_id();
280        assert!(!id.is_empty());
281
282        // Different redeem script should produce different ID
283        let witness2 = AddressWitness::P2sh {
284            signatures: vec![
285                BinaryData::new(vec![0x00]),
286                BinaryData::new(vec![0x30, 0x44]),
287            ],
288            redeem_script: BinaryData::new(vec![0x53, 0xae]),
289        };
290        assert_ne!(id, witness2.unique_id());
291    }
292
293    #[cfg(feature = "serde-conversion")]
294    #[test]
295    fn test_p2pkh_serde() {
296        let witness = AddressWitness::P2pkh {
297            signature: BinaryData::new(vec![0x30, 0x44, 0x02, 0x20]),
298        };
299
300        let json = serde_json::to_string(&witness).unwrap();
301        let deserialized: AddressWitness = serde_json::from_str(&json).unwrap();
302
303        assert_eq!(witness, deserialized);
304    }
305
306    #[cfg(feature = "serde-conversion")]
307    #[test]
308    fn test_p2sh_serde() {
309        let witness = AddressWitness::P2sh {
310            signatures: vec![
311                BinaryData::new(vec![0x00]),
312                BinaryData::new(vec![0x30, 0x44]),
313            ],
314            redeem_script: BinaryData::new(vec![0x52, 0xae]),
315        };
316
317        let json = serde_json::to_string(&witness).unwrap();
318        let deserialized: AddressWitness = serde_json::from_str(&json).unwrap();
319
320        assert_eq!(witness, deserialized);
321    }
322
323    /// AUDIT L1: Unbounded P2SH witness size during deserialization.
324    ///
325    /// The `Decode` impl for `AddressWitness::P2sh` now enforces
326    /// `MAX_P2SH_SIGNATURES` during deserialization. A payload with more
327    /// signatures than the limit is rejected with a decode error.
328    ///
329    /// Location: rs-dpp/src/address_funds/witness.rs
330    #[test]
331    fn test_p2sh_witness_rejects_excessive_signatures() {
332        // Create a P2SH witness with 1000 signatures — far above MAX_P2SH_SIGNATURES
333        let num_signatures = 1000;
334        let signatures: Vec<BinaryData> = (0..num_signatures)
335            .map(|i| BinaryData::new(vec![0x30, 0x44, i as u8]))
336            .collect();
337
338        let witness = AddressWitness::P2sh {
339            signatures,
340            redeem_script: BinaryData::new(vec![0x52, 0xae]),
341        };
342
343        // Encode succeeds (encoding has no limit), but decode must reject
344        let encoded = bincode::encode_to_vec(&witness, config::standard()).unwrap();
345        let result: Result<(AddressWitness, usize), _> =
346            bincode::decode_from_slice(&encoded, config::standard());
347
348        assert!(
349            result.is_err(),
350            "AUDIT L1: P2SH witness with {} signatures should be rejected during \
351            deserialization. MAX_P2SH_SIGNATURES = {}.",
352            num_signatures,
353            MAX_P2SH_SIGNATURES,
354        );
355    }
356
357    /// AUDIT L3: No maximum length check on P2SH signatures vector.
358    ///
359    /// The deserialization now enforces `MAX_P2SH_SIGNATURES` (17). Signature
360    /// counts above this limit are rejected during decode. The boundary value
361    /// (17) is accepted, and 18+ is rejected.
362    ///
363    /// Location: rs-dpp/src/address_funds/witness.rs
364    #[test]
365    fn test_p2sh_witness_max_signatures_boundary() {
366        // Counts above MAX_P2SH_SIGNATURES should be rejected during decode
367        for count in [50, 100, 500] {
368            let signatures: Vec<BinaryData> = (0..count)
369                .map(|_| BinaryData::new(vec![0x30, 0x44, 0x02, 0x20]))
370                .collect();
371
372            let witness = AddressWitness::P2sh {
373                signatures,
374                redeem_script: BinaryData::new(vec![0x52, 0xae]),
375            };
376
377            let encoded = bincode::encode_to_vec(&witness, config::standard()).unwrap();
378            let result: Result<(AddressWitness, usize), _> =
379                bincode::decode_from_slice(&encoded, config::standard());
380
381            assert!(
382                result.is_err(),
383                "AUDIT L3: P2SH witness with {} signatures should be rejected during \
384                deserialization. MAX_P2SH_SIGNATURES = {}.",
385                count,
386                MAX_P2SH_SIGNATURES,
387            );
388        }
389
390        // MAX_P2SH_SIGNATURES (17) should be accepted
391        let signatures: Vec<BinaryData> = (0..MAX_P2SH_SIGNATURES)
392            .map(|_| BinaryData::new(vec![0x30, 0x44, 0x02, 0x20]))
393            .collect();
394
395        let witness = AddressWitness::P2sh {
396            signatures,
397            redeem_script: BinaryData::new(vec![0x52, 0xae]),
398        };
399
400        let encoded = bincode::encode_to_vec(&witness, config::standard()).unwrap();
401        let decoded: AddressWitness = bincode::decode_from_slice(&encoded, config::standard())
402            .unwrap()
403            .0;
404
405        assert_eq!(witness, decoded);
406
407        // MAX_P2SH_SIGNATURES + 1 should be rejected
408        let signatures: Vec<BinaryData> = (0..MAX_P2SH_SIGNATURES + 1)
409            .map(|_| BinaryData::new(vec![0x30, 0x44, 0x02, 0x20]))
410            .collect();
411
412        let witness = AddressWitness::P2sh {
413            signatures,
414            redeem_script: BinaryData::new(vec![0x52, 0xae]),
415        };
416
417        let encoded = bincode::encode_to_vec(&witness, config::standard()).unwrap();
418        let result: Result<(AddressWitness, usize), _> =
419            bincode::decode_from_slice(&encoded, config::standard());
420
421        assert!(
422            result.is_err(),
423            "P2SH witness with {} signatures (MAX + 1) should be rejected",
424            MAX_P2SH_SIGNATURES + 1,
425        );
426    }
427
428    // --- Additional encode/decode round-trip tests ---
429
430    #[test]
431    fn test_p2pkh_empty_signature_round_trip() {
432        let witness = AddressWitness::P2pkh {
433            signature: BinaryData::new(vec![]),
434        };
435
436        let encoded = bincode::encode_to_vec(&witness, config::standard()).unwrap();
437        let decoded: AddressWitness = bincode::decode_from_slice(&encoded, config::standard())
438            .unwrap()
439            .0;
440
441        assert_eq!(witness, decoded);
442        assert!(decoded.is_p2pkh());
443    }
444
445    #[test]
446    fn test_p2pkh_65_byte_signature_round_trip() {
447        // Typical recoverable ECDSA signature is 65 bytes
448        let signature_data: Vec<u8> = (0..65).collect();
449        let witness = AddressWitness::P2pkh {
450            signature: BinaryData::new(signature_data),
451        };
452
453        let encoded = bincode::encode_to_vec(&witness, config::standard()).unwrap();
454        let decoded: AddressWitness = bincode::decode_from_slice(&encoded, config::standard())
455            .unwrap()
456            .0;
457
458        assert_eq!(witness, decoded);
459    }
460
461    #[test]
462    fn test_p2sh_single_signature_round_trip() {
463        let witness = AddressWitness::P2sh {
464            signatures: vec![BinaryData::new(vec![0x30, 0x44, 0x02, 0x20])],
465            redeem_script: BinaryData::new(vec![0x51, 0xae]),
466        };
467
468        let encoded = bincode::encode_to_vec(&witness, config::standard()).unwrap();
469        let decoded: AddressWitness = bincode::decode_from_slice(&encoded, config::standard())
470            .unwrap()
471            .0;
472
473        assert_eq!(witness, decoded);
474        assert!(decoded.is_p2sh());
475        assert_eq!(
476            decoded.redeem_script(),
477            Some(&BinaryData::new(vec![0x51, 0xae]))
478        );
479    }
480
481    #[test]
482    fn test_p2sh_empty_signatures_vec_round_trip() {
483        let witness = AddressWitness::P2sh {
484            signatures: vec![],
485            redeem_script: BinaryData::new(vec![0x52, 0xae]),
486        };
487
488        let encoded = bincode::encode_to_vec(&witness, config::standard()).unwrap();
489        let decoded: AddressWitness = bincode::decode_from_slice(&encoded, config::standard())
490            .unwrap()
491            .0;
492
493        assert_eq!(witness, decoded);
494    }
495
496    #[test]
497    fn test_p2sh_empty_redeem_script_round_trip() {
498        let witness = AddressWitness::P2sh {
499            signatures: vec![BinaryData::new(vec![0x00])],
500            redeem_script: BinaryData::new(vec![]),
501        };
502
503        let encoded = bincode::encode_to_vec(&witness, config::standard()).unwrap();
504        let decoded: AddressWitness = bincode::decode_from_slice(&encoded, config::standard())
505            .unwrap()
506            .0;
507
508        assert_eq!(witness, decoded);
509    }
510
511    // --- Error path tests ---
512
513    #[test]
514    fn test_invalid_discriminant_decode_fails() {
515        // Manually craft a payload with discriminant 2 (invalid)
516        let mut data = vec![];
517        bincode::encode_into_std_write(&2u8, &mut data, config::standard()).unwrap();
518        // Add some dummy data
519        data.extend_from_slice(&[0x00, 0x00, 0x00]);
520
521        let result: Result<(AddressWitness, usize), _> =
522            bincode::decode_from_slice(&data, config::standard());
523        assert!(result.is_err());
524        let err_msg = format!("{}", result.unwrap_err());
525        assert!(err_msg.contains("Invalid AddressWitness discriminant"));
526    }
527
528    #[test]
529    fn test_invalid_discriminant_255_decode_fails() {
530        let mut data = vec![];
531        bincode::encode_into_std_write(&255u8, &mut data, config::standard()).unwrap();
532
533        let result: Result<(AddressWitness, usize), _> =
534            bincode::decode_from_slice(&data, config::standard());
535        assert!(result.is_err());
536    }
537
538    #[test]
539    fn test_truncated_p2pkh_payload_fails() {
540        // Encode only the discriminant, no signature data
541        let data = vec![0u8]; // discriminant for P2pkh
542        let result: Result<(AddressWitness, usize), _> =
543            bincode::decode_from_slice(&data, config::standard());
544        assert!(result.is_err());
545    }
546
547    #[test]
548    fn test_truncated_p2sh_payload_fails() {
549        // Encode discriminant for P2sh but no signatures/redeem_script
550        let data = vec![1u8]; // discriminant for P2sh
551        let result: Result<(AddressWitness, usize), _> =
552            bincode::decode_from_slice(&data, config::standard());
553        assert!(result.is_err());
554    }
555
556    #[test]
557    fn test_empty_payload_fails() {
558        let data: Vec<u8> = vec![];
559        let result: Result<(AddressWitness, usize), _> =
560            bincode::decode_from_slice(&data, config::standard());
561        assert!(result.is_err());
562    }
563
564    // --- Accessor tests ---
565
566    #[test]
567    fn test_redeem_script_returns_none_for_p2pkh() {
568        let witness = AddressWitness::P2pkh {
569            signature: BinaryData::new(vec![0x30]),
570        };
571        assert!(witness.redeem_script().is_none());
572    }
573
574    #[test]
575    fn test_redeem_script_returns_some_for_p2sh() {
576        let script = BinaryData::new(vec![0x52, 0xae]);
577        let witness = AddressWitness::P2sh {
578            signatures: vec![],
579            redeem_script: script.clone(),
580        };
581        assert_eq!(witness.redeem_script(), Some(&script));
582    }
583
584    // --- BorrowDecode path tests ---
585
586    #[test]
587    fn test_borrow_decode_p2pkh_round_trip() {
588        let witness = AddressWitness::P2pkh {
589            signature: BinaryData::new(vec![0xAB, 0xCD, 0xEF]),
590        };
591
592        let encoded = bincode::encode_to_vec(&witness, config::standard()).unwrap();
593        // borrow_decode is exercised through decode_from_slice
594        let decoded: AddressWitness = bincode::decode_from_slice(&encoded, config::standard())
595            .unwrap()
596            .0;
597        assert_eq!(witness, decoded);
598    }
599
600    #[test]
601    fn test_borrow_decode_p2sh_round_trip() {
602        let witness = AddressWitness::P2sh {
603            signatures: vec![
604                BinaryData::new(vec![0x00]),
605                BinaryData::new(vec![0x30, 0x44]),
606                BinaryData::new(vec![0x30, 0x45]),
607            ],
608            redeem_script: BinaryData::new(vec![0x52, 0x53, 0xae]),
609        };
610
611        let encoded = bincode::encode_to_vec(&witness, config::standard()).unwrap();
612        let decoded: AddressWitness = bincode::decode_from_slice(&encoded, config::standard())
613            .unwrap()
614            .0;
615        assert_eq!(witness, decoded);
616    }
617
618    #[test]
619    fn test_borrow_decode_rejects_excessive_signatures() {
620        // Ensure BorrowDecode also rejects > MAX_P2SH_SIGNATURES
621        let signatures: Vec<BinaryData> = (0..MAX_P2SH_SIGNATURES + 1)
622            .map(|_| BinaryData::new(vec![0x30]))
623            .collect();
624
625        let witness = AddressWitness::P2sh {
626            signatures,
627            redeem_script: BinaryData::new(vec![0xae]),
628        };
629
630        let encoded = bincode::encode_to_vec(&witness, config::standard()).unwrap();
631        let result: Result<(AddressWitness, usize), _> =
632            bincode::decode_from_slice(&encoded, config::standard());
633        assert!(result.is_err());
634    }
635
636    #[test]
637    fn test_borrow_decode_invalid_discriminant_fails() {
638        let mut data = vec![];
639        bincode::encode_into_std_write(&3u8, &mut data, config::standard()).unwrap();
640        data.extend_from_slice(&[0x00; 10]);
641
642        let result: Result<(AddressWitness, usize), _> =
643            bincode::decode_from_slice(&data, config::standard());
644        assert!(result.is_err());
645    }
646}
647
648#[cfg(all(feature = "json-conversion", feature = "serde-conversion"))]
649impl crate::serialization::JsonConvertible for AddressWitness {}
650
651#[cfg(all(feature = "value-conversion", feature = "serde-conversion"))]
652impl crate::serialization::ValueConvertible for AddressWitness {}
653
654#[cfg(all(
655    test,
656    feature = "json-conversion",
657    feature = "value-conversion",
658    feature = "serde-conversion"
659))]
660mod json_convertible_tests {
661    use super::*;
662    use platform_value::{platform_value, BinaryData};
663    use serde_json::json;
664
665    // `AddressWitness` has a manual Serialize/Deserialize that emits a
666    // `{ "$type": "p2pkh"|"p2sh", ... }` discriminator shape. `BinaryData` is
667    // base64-encoded in JSON (HR), and stored as `Value::Bytes` in non-HR.
668
669    #[test]
670    fn json_round_trip_p2pkh_with_full_wire_shape() {
671        use crate::serialization::JsonConvertible;
672        let original = AddressWitness::P2pkh {
673            signature: BinaryData::new(vec![0xa1; 65]),
674        };
675        let json = original.to_json().expect("to_json");
676        assert_eq!(
677            json,
678            json!({
679                "$type": "p2pkh",
680                "signature": "oaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaE=",
681            })
682        );
683        let recovered = AddressWitness::from_json(json).expect("from_json");
684        assert_eq!(original, recovered);
685    }
686
687    #[test]
688    fn json_round_trip_p2sh_with_full_wire_shape() {
689        use crate::serialization::JsonConvertible;
690        let original = AddressWitness::P2sh {
691            redeem_script: BinaryData::new(vec![0xb2; 30]),
692            signatures: vec![BinaryData::new(vec![0xc3; 65])],
693        };
694        let json = original.to_json().expect("to_json");
695        assert_eq!(
696            json,
697            json!({
698                "$type": "p2sh",
699                "signatures": [
700                    "w8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8M=",
701                ],
702                "redeemScript": "srKysrKysrKysrKysrKysrKysrKysrKysrKysrKy",
703            })
704        );
705        let recovered = AddressWitness::from_json(json).expect("from_json");
706        assert_eq!(original, recovered);
707    }
708
709    #[test]
710    fn value_round_trip_p2pkh_with_full_wire_shape() {
711        use crate::serialization::ValueConvertible;
712        use platform_value::Value;
713        let original = AddressWitness::P2pkh {
714            signature: BinaryData::new(vec![0xa1; 65]),
715        };
716        let value = original.to_object().expect("to_object");
717        // `BinaryData` serializes as `Value::Bytes(Vec<u8>)` in non-HR mode.
718        assert_eq!(
719            value,
720            platform_value!({
721                "$type": "p2pkh",
722                "signature": Value::Bytes(vec![0xa1; 65]),
723            })
724        );
725        let recovered = AddressWitness::from_object(value).expect("from_object");
726        assert_eq!(original, recovered);
727    }
728
729    #[test]
730    fn value_round_trip_p2sh_with_full_wire_shape() {
731        use crate::serialization::ValueConvertible;
732        use platform_value::Value;
733        let original = AddressWitness::P2sh {
734            redeem_script: BinaryData::new(vec![0xb2; 30]),
735            signatures: vec![BinaryData::new(vec![0xc3; 65])],
736        };
737        let value = original.to_object().expect("to_object");
738        assert_eq!(
739            value,
740            platform_value!({
741                "$type": "p2sh",
742                "signatures": [Value::Bytes(vec![0xc3; 65])],
743                "redeemScript": Value::Bytes(vec![0xb2; 30]),
744            })
745        );
746        let recovered = AddressWitness::from_object(value).expect("from_object");
747        assert_eq!(original, recovered);
748    }
749}