1#[cfg(feature = "json-conversion")]
2use crate::serialization::json_safe_fields;
3#[cfg(all(feature = "json-conversion", feature = "serde-conversion"))]
4use crate::serialization::JsonConvertible;
5#[cfg(all(feature = "value-conversion", feature = "serde-conversion"))]
6use crate::serialization::ValueConvertible;
7use bincode::de::BorrowDecoder;
8use bincode::enc::Encoder;
9use bincode::error::{DecodeError, EncodeError};
10use bincode::Encode;
11use platform_value::BinaryData;
12#[cfg(feature = "serde-conversion")]
13use serde::{Deserialize, Serialize};
14
15pub const MAX_P2SH_SIGNATURES: usize = 17;
18
19#[cfg_attr(feature = "json-conversion", json_safe_fields)]
32#[derive(Debug, Clone, PartialEq, Ord, PartialOrd, Eq)]
33#[cfg_attr(
34 feature = "serde-conversion",
35 derive(Serialize, Deserialize),
36 serde(tag = "$type")
37)]
38pub enum AddressWitness {
39 #[cfg_attr(feature = "serde-conversion", serde(rename = "p2pkh"))]
45 P2pkh {
46 signature: BinaryData, },
49 #[cfg_attr(feature = "serde-conversion", serde(rename = "p2sh"))]
55 P2sh {
56 signatures: Vec<BinaryData>,
58 #[cfg_attr(feature = "serde-conversion", serde(rename = "redeemScript"))]
60 redeem_script: BinaryData,
61 },
62}
63
64impl Encode for AddressWitness {
65 fn encode<E: Encoder>(&self, encoder: &mut E) -> Result<(), EncodeError> {
66 match self {
67 AddressWitness::P2pkh { signature } => {
68 0u8.encode(encoder)?;
69 signature.encode(encoder)?;
70 }
71 AddressWitness::P2sh {
72 signatures,
73 redeem_script,
74 } => {
75 1u8.encode(encoder)?;
76 signatures.encode(encoder)?;
77 redeem_script.encode(encoder)?;
78 }
79 }
80 Ok(())
81 }
82}
83
84macro_rules! impl_address_witness_decode {
86 ($decode:ident, $decoder:ident, $method:ident, $untrusted:expr) => {
87 impl<C> bincode::$decode<C> for AddressWitness {
88 fn $method<D: bincode::de::$decoder<Context = C>>(
89 decoder: &mut D,
90 ) -> Result<Self, DecodeError> {
91 let discriminant = u8::$method(decoder)?;
92 match discriminant {
93 0 => {
94 let signature = BinaryData::$method(decoder)?;
95 Ok(AddressWitness::P2pkh { signature })
96 }
97 1 => {
98 let signatures = Vec::<BinaryData>::$method(decoder)?;
99 if signatures.len() > MAX_P2SH_SIGNATURES {
100 return Err(DecodeError::OtherString(format!(
101 "P2SH signatures count {} exceeds maximum {}",
102 signatures.len(),
103 MAX_P2SH_SIGNATURES,
104 )));
105 }
106 let redeem_script = BinaryData::$method(decoder)?;
107 Ok(AddressWitness::P2sh {
108 signatures,
109 redeem_script,
110 })
111 }
112 _ => Err(DecodeError::OtherString(format!(
113 "Invalid AddressWitness discriminant: {}",
114 discriminant
115 ))),
116 }
117 }
118 }
119 };
120}
121impl_address_witness_decode!(Decode, Decoder, decode, false);
122impl_address_witness_decode!(DecodeUntrusted, UntrustedDecoder, decode_untrusted, true);
123bincode::impl_borrow_decode_untrusted!(AddressWitness);
124
125impl<'de, C> bincode::BorrowDecode<'de, C> for AddressWitness {
126 fn borrow_decode<D: BorrowDecoder<'de, Context = C>>(
127 decoder: &mut D,
128 ) -> Result<Self, DecodeError> {
129 let discriminant = u8::borrow_decode(decoder)?;
130 match discriminant {
131 0 => {
132 let signature = BinaryData::borrow_decode(decoder)?;
133 Ok(AddressWitness::P2pkh { signature })
134 }
135 1 => {
136 let signatures = Vec::<BinaryData>::borrow_decode(decoder)?;
137 if signatures.len() > MAX_P2SH_SIGNATURES {
138 return Err(DecodeError::OtherString(format!(
139 "P2SH signatures count {} exceeds maximum {}",
140 signatures.len(),
141 MAX_P2SH_SIGNATURES,
142 )));
143 }
144 let redeem_script = BinaryData::borrow_decode(decoder)?;
145 Ok(AddressWitness::P2sh {
146 signatures,
147 redeem_script,
148 })
149 }
150 _ => Err(DecodeError::OtherString(format!(
151 "Invalid AddressWitness discriminant: {}",
152 discriminant
153 ))),
154 }
155 }
156}
157
158impl AddressWitness {
159 pub fn unique_id(&self) -> String {
163 use base64::prelude::BASE64_STANDARD;
164 use base64::Engine;
165
166 let mut data = Vec::new();
167
168 match self {
169 AddressWitness::P2pkh { signature } => {
170 data.push(0u8);
171 data.extend_from_slice(signature.as_slice());
172 }
173 AddressWitness::P2sh {
174 signatures,
175 redeem_script,
176 } => {
177 data.push(1u8);
178 data.extend_from_slice(redeem_script.as_slice());
179 for sig in signatures {
180 data.extend_from_slice(sig.as_slice());
181 }
182 }
183 }
184
185 BASE64_STANDARD.encode(&data)
186 }
187
188 pub fn redeem_script(&self) -> Option<&BinaryData> {
190 match self {
191 AddressWitness::P2pkh { .. } => None,
192 AddressWitness::P2sh { redeem_script, .. } => Some(redeem_script),
193 }
194 }
195
196 pub fn is_p2pkh(&self) -> bool {
198 matches!(self, AddressWitness::P2pkh { .. })
199 }
200
201 pub fn is_p2sh(&self) -> bool {
203 matches!(self, AddressWitness::P2sh { .. })
204 }
205}
206
207#[cfg(test)]
208#[allow(clippy::needless_borrows_for_generic_args)]
209mod tests {
210 use super::*;
211 use bincode::config;
212
213 #[test]
214 fn test_p2pkh_witness_encode_decode() {
215 let witness = AddressWitness::P2pkh {
216 signature: BinaryData::new(vec![0x30, 0x44, 0x02, 0x20]),
217 };
218
219 let encoded = bincode::encode_to_vec(&witness, config::standard()).unwrap();
220 let decoded: AddressWitness = bincode::decode_from_slice(&encoded, config::standard())
221 .unwrap()
222 .0;
223
224 assert_eq!(witness, decoded);
225 assert!(decoded.is_p2pkh());
226 assert!(!decoded.is_p2sh());
227 }
228
229 #[test]
230 fn test_p2sh_witness_encode_decode() {
231 let witness = AddressWitness::P2sh {
232 signatures: vec![
233 BinaryData::new(vec![0x00]), BinaryData::new(vec![0x30, 0x44, 0x02, 0x20]), BinaryData::new(vec![0x30, 0x45, 0x02, 0x21]), ],
237 redeem_script: BinaryData::new(vec![
238 0x52, 0x21, 0x02, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12,
241 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12,
242 0x12, 0x12, 0x12, 0x12, 0x12, 0x53, 0xae, ]),
245 };
246
247 let encoded = bincode::encode_to_vec(&witness, config::standard()).unwrap();
248 let decoded: AddressWitness = bincode::decode_from_slice(&encoded, config::standard())
249 .unwrap()
250 .0;
251
252 assert_eq!(witness, decoded);
253 assert!(!decoded.is_p2pkh());
254 assert!(decoded.is_p2sh());
255 }
256
257 #[test]
258 fn test_unique_id_p2pkh() {
259 let witness = AddressWitness::P2pkh {
260 signature: BinaryData::new(vec![0x30, 0x44]),
261 };
262
263 let id = witness.unique_id();
264 assert!(!id.is_empty());
265
266 let witness2 = AddressWitness::P2pkh {
268 signature: BinaryData::new(vec![0x30, 0x45]),
269 };
270 assert_ne!(id, witness2.unique_id());
271 }
272
273 #[test]
274 fn test_unique_id_p2sh() {
275 let witness = AddressWitness::P2sh {
276 signatures: vec![
277 BinaryData::new(vec![0x00]),
278 BinaryData::new(vec![0x30, 0x44]),
279 ],
280 redeem_script: BinaryData::new(vec![0x52, 0xae]),
281 };
282
283 let id = witness.unique_id();
284 assert!(!id.is_empty());
285
286 let witness2 = AddressWitness::P2sh {
288 signatures: vec![
289 BinaryData::new(vec![0x00]),
290 BinaryData::new(vec![0x30, 0x44]),
291 ],
292 redeem_script: BinaryData::new(vec![0x53, 0xae]),
293 };
294 assert_ne!(id, witness2.unique_id());
295 }
296
297 #[cfg(feature = "serde-conversion")]
298 #[test]
299 fn test_p2pkh_serde() {
300 let witness = AddressWitness::P2pkh {
301 signature: BinaryData::new(vec![0x30, 0x44, 0x02, 0x20]),
302 };
303
304 let json = serde_json::to_string(&witness).unwrap();
305 let deserialized: AddressWitness = serde_json::from_str(&json).unwrap();
306
307 assert_eq!(witness, deserialized);
308 }
309
310 #[cfg(feature = "serde-conversion")]
311 #[test]
312 fn test_p2sh_serde() {
313 let witness = AddressWitness::P2sh {
314 signatures: vec![
315 BinaryData::new(vec![0x00]),
316 BinaryData::new(vec![0x30, 0x44]),
317 ],
318 redeem_script: BinaryData::new(vec![0x52, 0xae]),
319 };
320
321 let json = serde_json::to_string(&witness).unwrap();
322 let deserialized: AddressWitness = serde_json::from_str(&json).unwrap();
323
324 assert_eq!(witness, deserialized);
325 }
326
327 #[test]
335 fn test_p2sh_witness_rejects_excessive_signatures() {
336 let num_signatures = 1000;
338 let signatures: Vec<BinaryData> = (0..num_signatures)
339 .map(|i| BinaryData::new(vec![0x30, 0x44, i as u8]))
340 .collect();
341
342 let witness = AddressWitness::P2sh {
343 signatures,
344 redeem_script: BinaryData::new(vec![0x52, 0xae]),
345 };
346
347 let encoded = bincode::encode_to_vec(&witness, config::standard()).unwrap();
349 let result: Result<(AddressWitness, usize), _> =
350 bincode::decode_from_slice(&encoded, config::standard());
351
352 assert!(
353 result.is_err(),
354 "AUDIT L1: P2SH witness with {} signatures should be rejected during \
355 deserialization. MAX_P2SH_SIGNATURES = {}.",
356 num_signatures,
357 MAX_P2SH_SIGNATURES,
358 );
359 }
360
361 #[test]
369 fn test_p2sh_witness_max_signatures_boundary() {
370 for count in [50, 100, 500] {
372 let signatures: Vec<BinaryData> = (0..count)
373 .map(|_| BinaryData::new(vec![0x30, 0x44, 0x02, 0x20]))
374 .collect();
375
376 let witness = AddressWitness::P2sh {
377 signatures,
378 redeem_script: BinaryData::new(vec![0x52, 0xae]),
379 };
380
381 let encoded = bincode::encode_to_vec(&witness, config::standard()).unwrap();
382 let result: Result<(AddressWitness, usize), _> =
383 bincode::decode_from_slice(&encoded, config::standard());
384
385 assert!(
386 result.is_err(),
387 "AUDIT L3: P2SH witness with {} signatures should be rejected during \
388 deserialization. MAX_P2SH_SIGNATURES = {}.",
389 count,
390 MAX_P2SH_SIGNATURES,
391 );
392 }
393
394 let signatures: Vec<BinaryData> = (0..MAX_P2SH_SIGNATURES)
396 .map(|_| BinaryData::new(vec![0x30, 0x44, 0x02, 0x20]))
397 .collect();
398
399 let witness = AddressWitness::P2sh {
400 signatures,
401 redeem_script: BinaryData::new(vec![0x52, 0xae]),
402 };
403
404 let encoded = bincode::encode_to_vec(&witness, config::standard()).unwrap();
405 let decoded: AddressWitness = bincode::decode_from_slice(&encoded, config::standard())
406 .unwrap()
407 .0;
408
409 assert_eq!(witness, decoded);
410
411 let signatures: Vec<BinaryData> = (0..MAX_P2SH_SIGNATURES + 1)
413 .map(|_| BinaryData::new(vec![0x30, 0x44, 0x02, 0x20]))
414 .collect();
415
416 let witness = AddressWitness::P2sh {
417 signatures,
418 redeem_script: BinaryData::new(vec![0x52, 0xae]),
419 };
420
421 let encoded = bincode::encode_to_vec(&witness, config::standard()).unwrap();
422 let result: Result<(AddressWitness, usize), _> =
423 bincode::decode_from_slice(&encoded, config::standard());
424
425 assert!(
426 result.is_err(),
427 "P2SH witness with {} signatures (MAX + 1) should be rejected",
428 MAX_P2SH_SIGNATURES + 1,
429 );
430 }
431
432 #[test]
435 fn test_p2pkh_empty_signature_round_trip() {
436 let witness = AddressWitness::P2pkh {
437 signature: BinaryData::new(vec![]),
438 };
439
440 let encoded = bincode::encode_to_vec(&witness, config::standard()).unwrap();
441 let decoded: AddressWitness = bincode::decode_from_slice(&encoded, config::standard())
442 .unwrap()
443 .0;
444
445 assert_eq!(witness, decoded);
446 assert!(decoded.is_p2pkh());
447 }
448
449 #[test]
450 fn test_p2pkh_65_byte_signature_round_trip() {
451 let signature_data: Vec<u8> = (0..65).collect();
453 let witness = AddressWitness::P2pkh {
454 signature: BinaryData::new(signature_data),
455 };
456
457 let encoded = bincode::encode_to_vec(&witness, config::standard()).unwrap();
458 let decoded: AddressWitness = bincode::decode_from_slice(&encoded, config::standard())
459 .unwrap()
460 .0;
461
462 assert_eq!(witness, decoded);
463 }
464
465 #[test]
466 fn test_p2sh_single_signature_round_trip() {
467 let witness = AddressWitness::P2sh {
468 signatures: vec![BinaryData::new(vec![0x30, 0x44, 0x02, 0x20])],
469 redeem_script: BinaryData::new(vec![0x51, 0xae]),
470 };
471
472 let encoded = bincode::encode_to_vec(&witness, config::standard()).unwrap();
473 let decoded: AddressWitness = bincode::decode_from_slice(&encoded, config::standard())
474 .unwrap()
475 .0;
476
477 assert_eq!(witness, decoded);
478 assert!(decoded.is_p2sh());
479 assert_eq!(
480 decoded.redeem_script(),
481 Some(&BinaryData::new(vec![0x51, 0xae]))
482 );
483 }
484
485 #[test]
486 fn test_p2sh_empty_signatures_vec_round_trip() {
487 let witness = AddressWitness::P2sh {
488 signatures: vec![],
489 redeem_script: BinaryData::new(vec![0x52, 0xae]),
490 };
491
492 let encoded = bincode::encode_to_vec(&witness, config::standard()).unwrap();
493 let decoded: AddressWitness = bincode::decode_from_slice(&encoded, config::standard())
494 .unwrap()
495 .0;
496
497 assert_eq!(witness, decoded);
498 }
499
500 #[test]
501 fn test_p2sh_empty_redeem_script_round_trip() {
502 let witness = AddressWitness::P2sh {
503 signatures: vec![BinaryData::new(vec![0x00])],
504 redeem_script: BinaryData::new(vec![]),
505 };
506
507 let encoded = bincode::encode_to_vec(&witness, config::standard()).unwrap();
508 let decoded: AddressWitness = bincode::decode_from_slice(&encoded, config::standard())
509 .unwrap()
510 .0;
511
512 assert_eq!(witness, decoded);
513 }
514
515 #[test]
518 fn test_invalid_discriminant_decode_fails() {
519 let mut data = vec![];
521 bincode::encode_into_std_write(&2u8, &mut data, config::standard()).unwrap();
522 data.extend_from_slice(&[0x00, 0x00, 0x00]);
524
525 let result: Result<(AddressWitness, usize), _> =
526 bincode::decode_from_slice(&data, config::standard());
527 assert!(result.is_err());
528 let err_msg = format!("{}", result.unwrap_err());
529 assert!(err_msg.contains("Invalid AddressWitness discriminant"));
530 }
531
532 #[test]
533 fn test_invalid_discriminant_255_decode_fails() {
534 let mut data = vec![];
535 bincode::encode_into_std_write(&255u8, &mut data, config::standard()).unwrap();
536
537 let result: Result<(AddressWitness, usize), _> =
538 bincode::decode_from_slice(&data, config::standard());
539 assert!(result.is_err());
540 }
541
542 #[test]
543 fn test_truncated_p2pkh_payload_fails() {
544 let data = vec![0u8]; let result: Result<(AddressWitness, usize), _> =
547 bincode::decode_from_slice(&data, config::standard());
548 assert!(result.is_err());
549 }
550
551 #[test]
552 fn test_truncated_p2sh_payload_fails() {
553 let data = vec![1u8]; let result: Result<(AddressWitness, usize), _> =
556 bincode::decode_from_slice(&data, config::standard());
557 assert!(result.is_err());
558 }
559
560 #[test]
561 fn test_empty_payload_fails() {
562 let data: Vec<u8> = vec![];
563 let result: Result<(AddressWitness, usize), _> =
564 bincode::decode_from_slice(&data, config::standard());
565 assert!(result.is_err());
566 }
567
568 #[test]
571 fn test_redeem_script_returns_none_for_p2pkh() {
572 let witness = AddressWitness::P2pkh {
573 signature: BinaryData::new(vec![0x30]),
574 };
575 assert!(witness.redeem_script().is_none());
576 }
577
578 #[test]
579 fn test_redeem_script_returns_some_for_p2sh() {
580 let script = BinaryData::new(vec![0x52, 0xae]);
581 let witness = AddressWitness::P2sh {
582 signatures: vec![],
583 redeem_script: script.clone(),
584 };
585 assert_eq!(witness.redeem_script(), Some(&script));
586 }
587
588 #[test]
591 fn test_borrow_decode_p2pkh_round_trip() {
592 let witness = AddressWitness::P2pkh {
593 signature: BinaryData::new(vec![0xAB, 0xCD, 0xEF]),
594 };
595
596 let encoded = bincode::encode_to_vec(&witness, config::standard()).unwrap();
597 let decoded: AddressWitness = bincode::decode_from_slice(&encoded, config::standard())
599 .unwrap()
600 .0;
601 assert_eq!(witness, decoded);
602 }
603
604 #[test]
605 fn test_borrow_decode_p2sh_round_trip() {
606 let witness = AddressWitness::P2sh {
607 signatures: vec![
608 BinaryData::new(vec![0x00]),
609 BinaryData::new(vec![0x30, 0x44]),
610 BinaryData::new(vec![0x30, 0x45]),
611 ],
612 redeem_script: BinaryData::new(vec![0x52, 0x53, 0xae]),
613 };
614
615 let encoded = bincode::encode_to_vec(&witness, config::standard()).unwrap();
616 let decoded: AddressWitness = bincode::decode_from_slice(&encoded, config::standard())
617 .unwrap()
618 .0;
619 assert_eq!(witness, decoded);
620 }
621
622 #[test]
623 fn test_borrow_decode_rejects_excessive_signatures() {
624 let signatures: Vec<BinaryData> = (0..MAX_P2SH_SIGNATURES + 1)
626 .map(|_| BinaryData::new(vec![0x30]))
627 .collect();
628
629 let witness = AddressWitness::P2sh {
630 signatures,
631 redeem_script: BinaryData::new(vec![0xae]),
632 };
633
634 let encoded = bincode::encode_to_vec(&witness, config::standard()).unwrap();
635 let result: Result<(AddressWitness, usize), _> =
636 bincode::decode_from_slice(&encoded, config::standard());
637 assert!(result.is_err());
638 }
639
640 #[test]
641 fn test_borrow_decode_invalid_discriminant_fails() {
642 let mut data = vec![];
643 bincode::encode_into_std_write(&3u8, &mut data, config::standard()).unwrap();
644 data.extend_from_slice(&[0x00; 10]);
645
646 let result: Result<(AddressWitness, usize), _> =
647 bincode::decode_from_slice(&data, config::standard());
648 assert!(result.is_err());
649 }
650}
651
652#[cfg(all(feature = "json-conversion", feature = "serde-conversion"))]
653impl JsonConvertible for AddressWitness {}
654
655#[cfg(all(feature = "value-conversion", feature = "serde-conversion"))]
656impl ValueConvertible for AddressWitness {}
657
658#[cfg(all(
659 test,
660 feature = "json-conversion",
661 feature = "value-conversion",
662 feature = "serde-conversion"
663))]
664mod json_convertible_tests {
665 use super::*;
666 use platform_value::{platform_value, BinaryData};
667 use serde_json::json;
668
669 #[test]
674 fn json_round_trip_p2pkh_with_full_wire_shape() {
675 use crate::serialization::JsonConvertible;
676 let original = AddressWitness::P2pkh {
677 signature: BinaryData::new(vec![0xa1; 65]),
678 };
679 let json = original.to_json().expect("to_json");
680 assert_eq!(
681 json,
682 json!({
683 "$type": "p2pkh",
684 "signature": "oaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaE=",
685 })
686 );
687 let recovered = AddressWitness::from_json(json).expect("from_json");
688 assert_eq!(original, recovered);
689 }
690
691 #[test]
692 fn json_round_trip_p2sh_with_full_wire_shape() {
693 use crate::serialization::JsonConvertible;
694 let original = AddressWitness::P2sh {
695 redeem_script: BinaryData::new(vec![0xb2; 30]),
696 signatures: vec![BinaryData::new(vec![0xc3; 65])],
697 };
698 let json = original.to_json().expect("to_json");
699 assert_eq!(
700 json,
701 json!({
702 "$type": "p2sh",
703 "signatures": [
704 "w8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8M=",
705 ],
706 "redeemScript": "srKysrKysrKysrKysrKysrKysrKysrKysrKysrKy",
707 })
708 );
709 let recovered = AddressWitness::from_json(json).expect("from_json");
710 assert_eq!(original, recovered);
711 }
712
713 #[test]
714 fn value_round_trip_p2pkh_with_full_wire_shape() {
715 use crate::serialization::ValueConvertible;
716 use platform_value::Value;
717 let original = AddressWitness::P2pkh {
718 signature: BinaryData::new(vec![0xa1; 65]),
719 };
720 let value = original.to_object().expect("to_object");
721 assert_eq!(
723 value,
724 platform_value!({
725 "$type": "p2pkh",
726 "signature": Value::Bytes(vec![0xa1; 65]),
727 })
728 );
729 let recovered = AddressWitness::from_object(value).expect("from_object");
730 assert_eq!(original, recovered);
731 }
732
733 #[test]
734 fn value_round_trip_p2sh_with_full_wire_shape() {
735 use crate::serialization::ValueConvertible;
736 use platform_value::Value;
737 let original = AddressWitness::P2sh {
738 redeem_script: BinaryData::new(vec![0xb2; 30]),
739 signatures: vec![BinaryData::new(vec![0xc3; 65])],
740 };
741 let value = original.to_object().expect("to_object");
742 assert_eq!(
743 value,
744 platform_value!({
745 "$type": "p2sh",
746 "signatures": [Value::Bytes(vec![0xc3; 65])],
747 "redeemScript": Value::Bytes(vec![0xb2; 30]),
748 })
749 );
750 let recovered = AddressWitness::from_object(value).expect("from_object");
751 assert_eq!(original, recovered);
752 }
753}