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
11pub const MAX_P2SH_SIGNATURES: usize = 17;
14
15#[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 #[cfg_attr(feature = "serde-conversion", serde(rename = "p2pkh"))]
41 P2pkh {
42 signature: BinaryData, },
45 #[cfg_attr(feature = "serde-conversion", serde(rename = "p2sh"))]
51 P2sh {
52 signatures: Vec<BinaryData>,
54 #[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
80macro_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 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 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 pub fn is_p2pkh(&self) -> bool {
194 matches!(self, AddressWitness::P2pkh { .. })
195 }
196
197 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]), BinaryData::new(vec![0x30, 0x44, 0x02, 0x20]), BinaryData::new(vec![0x30, 0x45, 0x02, 0x21]), ],
233 redeem_script: BinaryData::new(vec![
234 0x52, 0x21, 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, 0xae, ]),
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 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 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 #[test]
331 fn test_p2sh_witness_rejects_excessive_signatures() {
332 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 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 #[test]
365 fn test_p2sh_witness_max_signatures_boundary() {
366 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 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 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 #[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 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 #[test]
514 fn test_invalid_discriminant_decode_fails() {
515 let mut data = vec![];
517 bincode::encode_into_std_write(&2u8, &mut data, config::standard()).unwrap();
518 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 let data = vec![0u8]; 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 let data = vec![1u8]; 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 #[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 #[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 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 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 #[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 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}