Skip to main content

dpp/address_funds/
witness.rs

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