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
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
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 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 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 pub fn is_p2pkh(&self) -> bool {
184 matches!(self, AddressWitness::P2pkh { .. })
185 }
186
187 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]), BinaryData::new(vec![0x30, 0x44, 0x02, 0x20]), BinaryData::new(vec![0x30, 0x45, 0x02, 0x21]), ],
223 redeem_script: BinaryData::new(vec![
224 0x52, 0x21, 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, 0xae, ]),
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 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 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 #[test]
321 fn test_p2sh_witness_rejects_excessive_signatures() {
322 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 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 #[test]
355 fn test_p2sh_witness_max_signatures_boundary() {
356 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 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 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 #[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 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 #[test]
504 fn test_invalid_discriminant_decode_fails() {
505 let mut data = vec![];
507 bincode::encode_into_std_write(&2u8, &mut data, config::standard()).unwrap();
508 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 let data = vec![0u8]; 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 let data = vec![1u8]; 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 #[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 #[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 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 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 #[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 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}