1use bincode::de::{BorrowDecoder, Decoder};
2use bincode::enc::Encoder;
3use bincode::error::{DecodeError, EncodeError};
4use bincode::{Decode, Encode};
5use platform_value::BinaryData;
6#[cfg(feature = "serde-conversion")]
7use serde::{Deserialize, Serialize};
8
9pub const MAX_P2SH_SIGNATURES: usize = 17;
12
13#[derive(Debug, Clone, PartialEq, Ord, PartialOrd, Eq)]
17pub enum AddressWitness {
18 P2pkh {
24 signature: BinaryData, },
27 P2sh {
33 signatures: Vec<BinaryData>,
35 redeem_script: BinaryData,
37 },
38}
39
40impl Encode for AddressWitness {
41 fn encode<E: Encoder>(&self, encoder: &mut E) -> Result<(), EncodeError> {
42 match self {
43 AddressWitness::P2pkh { signature } => {
44 0u8.encode(encoder)?;
45 signature.encode(encoder)?;
46 }
47 AddressWitness::P2sh {
48 signatures,
49 redeem_script,
50 } => {
51 1u8.encode(encoder)?;
52 signatures.encode(encoder)?;
53 redeem_script.encode(encoder)?;
54 }
55 }
56 Ok(())
57 }
58}
59
60impl<C> Decode<C> for AddressWitness {
61 fn decode<D: Decoder<Context = C>>(decoder: &mut D) -> Result<Self, DecodeError> {
62 let discriminant = u8::decode(decoder)?;
63 match discriminant {
64 0 => {
65 let signature = BinaryData::decode(decoder)?;
66 Ok(AddressWitness::P2pkh { signature })
67 }
68 1 => {
69 let signatures = Vec::<BinaryData>::decode(decoder)?;
70 if signatures.len() > MAX_P2SH_SIGNATURES {
71 return Err(DecodeError::OtherString(format!(
72 "P2SH signatures count {} exceeds maximum {}",
73 signatures.len(),
74 MAX_P2SH_SIGNATURES,
75 )));
76 }
77 let redeem_script = BinaryData::decode(decoder)?;
78 Ok(AddressWitness::P2sh {
79 signatures,
80 redeem_script,
81 })
82 }
83 _ => Err(DecodeError::OtherString(format!(
84 "Invalid AddressWitness discriminant: {}",
85 discriminant
86 ))),
87 }
88 }
89}
90
91impl<'de, C> bincode::BorrowDecode<'de, C> for AddressWitness {
92 fn borrow_decode<D: BorrowDecoder<'de, Context = C>>(
93 decoder: &mut D,
94 ) -> Result<Self, DecodeError> {
95 let discriminant = u8::borrow_decode(decoder)?;
96 match discriminant {
97 0 => {
98 let signature = BinaryData::borrow_decode(decoder)?;
99 Ok(AddressWitness::P2pkh { signature })
100 }
101 1 => {
102 let signatures = Vec::<BinaryData>::borrow_decode(decoder)?;
103 if signatures.len() > MAX_P2SH_SIGNATURES {
104 return Err(DecodeError::OtherString(format!(
105 "P2SH signatures count {} exceeds maximum {}",
106 signatures.len(),
107 MAX_P2SH_SIGNATURES,
108 )));
109 }
110 let redeem_script = BinaryData::borrow_decode(decoder)?;
111 Ok(AddressWitness::P2sh {
112 signatures,
113 redeem_script,
114 })
115 }
116 _ => Err(DecodeError::OtherString(format!(
117 "Invalid AddressWitness discriminant: {}",
118 discriminant
119 ))),
120 }
121 }
122}
123
124#[cfg(feature = "serde-conversion")]
125impl Serialize for AddressWitness {
126 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
127 where
128 S: serde::Serializer,
129 {
130 use serde::ser::SerializeStruct;
131
132 match self {
133 AddressWitness::P2pkh { signature } => {
134 let mut state = serializer.serialize_struct("AddressWitness", 2)?;
135 state.serialize_field("type", "p2pkh")?;
136 state.serialize_field("signature", signature)?;
137 state.end()
138 }
139 AddressWitness::P2sh {
140 signatures,
141 redeem_script,
142 } => {
143 let mut state = serializer.serialize_struct("AddressWitness", 3)?;
144 state.serialize_field("type", "p2sh")?;
145 state.serialize_field("signatures", signatures)?;
146 state.serialize_field("redeemScript", redeem_script)?;
147 state.end()
148 }
149 }
150 }
151}
152
153#[cfg(feature = "serde-conversion")]
154impl<'de> Deserialize<'de> for AddressWitness {
155 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
156 where
157 D: serde::Deserializer<'de>,
158 {
159 use serde::de::{self, MapAccess, Visitor};
160 use std::fmt;
161
162 struct AddressWitnessVisitor;
163
164 impl<'de> Visitor<'de> for AddressWitnessVisitor {
165 type Value = AddressWitness;
166
167 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
168 formatter.write_str("an AddressWitness struct")
169 }
170
171 fn visit_map<V>(self, mut map: V) -> Result<AddressWitness, V::Error>
172 where
173 V: MapAccess<'de>,
174 {
175 let mut witness_type: Option<String> = None;
176 let mut signature: Option<BinaryData> = None;
177 let mut signatures: Option<Vec<BinaryData>> = None;
178 let mut redeem_script: Option<BinaryData> = None;
179
180 while let Some(key) = map.next_key::<String>()? {
181 match key.as_str() {
182 "type" => {
183 witness_type = Some(map.next_value()?);
184 }
185 "signature" => {
186 signature = Some(map.next_value()?);
187 }
188 "signatures" => {
189 signatures = Some(map.next_value()?);
190 }
191 "redeemScript" => {
192 redeem_script = Some(map.next_value()?);
193 }
194 _ => {
195 let _: serde::de::IgnoredAny = map.next_value()?;
196 }
197 }
198 }
199
200 let witness_type = witness_type.ok_or_else(|| de::Error::missing_field("type"))?;
201
202 match witness_type.as_str() {
203 "p2pkh" => {
204 let signature =
205 signature.ok_or_else(|| de::Error::missing_field("signature"))?;
206 Ok(AddressWitness::P2pkh { signature })
207 }
208 "p2sh" => {
209 let signatures =
210 signatures.ok_or_else(|| de::Error::missing_field("signatures"))?;
211 if signatures.len() > MAX_P2SH_SIGNATURES {
212 return Err(de::Error::custom(format!(
213 "P2SH signatures count {} exceeds maximum {}",
214 signatures.len(),
215 MAX_P2SH_SIGNATURES,
216 )));
217 }
218 let redeem_script = redeem_script
219 .ok_or_else(|| de::Error::missing_field("redeemScript"))?;
220 Ok(AddressWitness::P2sh {
221 signatures,
222 redeem_script,
223 })
224 }
225 _ => Err(de::Error::unknown_variant(
226 &witness_type,
227 &["p2pkh", "p2sh"],
228 )),
229 }
230 }
231 }
232
233 deserializer.deserialize_struct(
234 "AddressWitness",
235 &["type", "signature", "signatures", "redeemScript"],
236 AddressWitnessVisitor,
237 )
238 }
239}
240
241impl AddressWitness {
242 pub fn unique_id(&self) -> String {
246 use base64::prelude::BASE64_STANDARD;
247 use base64::Engine;
248
249 let mut data = Vec::new();
250
251 match self {
252 AddressWitness::P2pkh { signature } => {
253 data.push(0u8);
254 data.extend_from_slice(signature.as_slice());
255 }
256 AddressWitness::P2sh {
257 signatures,
258 redeem_script,
259 } => {
260 data.push(1u8);
261 data.extend_from_slice(redeem_script.as_slice());
262 for sig in signatures {
263 data.extend_from_slice(sig.as_slice());
264 }
265 }
266 }
267
268 BASE64_STANDARD.encode(&data)
269 }
270
271 pub fn redeem_script(&self) -> Option<&BinaryData> {
273 match self {
274 AddressWitness::P2pkh { .. } => None,
275 AddressWitness::P2sh { redeem_script, .. } => Some(redeem_script),
276 }
277 }
278
279 pub fn is_p2pkh(&self) -> bool {
281 matches!(self, AddressWitness::P2pkh { .. })
282 }
283
284 pub fn is_p2sh(&self) -> bool {
286 matches!(self, AddressWitness::P2sh { .. })
287 }
288}
289
290#[cfg(test)]
291mod tests {
292 use super::*;
293 use bincode::config;
294
295 #[test]
296 fn test_p2pkh_witness_encode_decode() {
297 let witness = AddressWitness::P2pkh {
298 signature: BinaryData::new(vec![0x30, 0x44, 0x02, 0x20]),
299 };
300
301 let encoded = bincode::encode_to_vec(&witness, config::standard()).unwrap();
302 let decoded: AddressWitness = bincode::decode_from_slice(&encoded, config::standard())
303 .unwrap()
304 .0;
305
306 assert_eq!(witness, decoded);
307 assert!(decoded.is_p2pkh());
308 assert!(!decoded.is_p2sh());
309 }
310
311 #[test]
312 fn test_p2sh_witness_encode_decode() {
313 let witness = AddressWitness::P2sh {
314 signatures: vec![
315 BinaryData::new(vec![0x00]), BinaryData::new(vec![0x30, 0x44, 0x02, 0x20]), BinaryData::new(vec![0x30, 0x45, 0x02, 0x21]), ],
319 redeem_script: BinaryData::new(vec![
320 0x52, 0x21, 0x02, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12,
323 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12,
324 0x12, 0x12, 0x12, 0x12, 0x12, 0x53, 0xae, ]),
327 };
328
329 let encoded = bincode::encode_to_vec(&witness, config::standard()).unwrap();
330 let decoded: AddressWitness = bincode::decode_from_slice(&encoded, config::standard())
331 .unwrap()
332 .0;
333
334 assert_eq!(witness, decoded);
335 assert!(!decoded.is_p2pkh());
336 assert!(decoded.is_p2sh());
337 }
338
339 #[test]
340 fn test_unique_id_p2pkh() {
341 let witness = AddressWitness::P2pkh {
342 signature: BinaryData::new(vec![0x30, 0x44]),
343 };
344
345 let id = witness.unique_id();
346 assert!(!id.is_empty());
347
348 let witness2 = AddressWitness::P2pkh {
350 signature: BinaryData::new(vec![0x30, 0x45]),
351 };
352 assert_ne!(id, witness2.unique_id());
353 }
354
355 #[test]
356 fn test_unique_id_p2sh() {
357 let witness = AddressWitness::P2sh {
358 signatures: vec![
359 BinaryData::new(vec![0x00]),
360 BinaryData::new(vec![0x30, 0x44]),
361 ],
362 redeem_script: BinaryData::new(vec![0x52, 0xae]),
363 };
364
365 let id = witness.unique_id();
366 assert!(!id.is_empty());
367
368 let witness2 = AddressWitness::P2sh {
370 signatures: vec![
371 BinaryData::new(vec![0x00]),
372 BinaryData::new(vec![0x30, 0x44]),
373 ],
374 redeem_script: BinaryData::new(vec![0x53, 0xae]),
375 };
376 assert_ne!(id, witness2.unique_id());
377 }
378
379 #[cfg(feature = "serde-conversion")]
380 #[test]
381 fn test_p2pkh_serde() {
382 let witness = AddressWitness::P2pkh {
383 signature: BinaryData::new(vec![0x30, 0x44, 0x02, 0x20]),
384 };
385
386 let json = serde_json::to_string(&witness).unwrap();
387 let deserialized: AddressWitness = serde_json::from_str(&json).unwrap();
388
389 assert_eq!(witness, deserialized);
390 }
391
392 #[cfg(feature = "serde-conversion")]
393 #[test]
394 fn test_p2sh_serde() {
395 let witness = AddressWitness::P2sh {
396 signatures: vec![
397 BinaryData::new(vec![0x00]),
398 BinaryData::new(vec![0x30, 0x44]),
399 ],
400 redeem_script: BinaryData::new(vec![0x52, 0xae]),
401 };
402
403 let json = serde_json::to_string(&witness).unwrap();
404 let deserialized: AddressWitness = serde_json::from_str(&json).unwrap();
405
406 assert_eq!(witness, deserialized);
407 }
408
409 #[test]
417 fn test_p2sh_witness_rejects_excessive_signatures() {
418 let num_signatures = 1000;
420 let signatures: Vec<BinaryData> = (0..num_signatures)
421 .map(|i| BinaryData::new(vec![0x30, 0x44, i as u8]))
422 .collect();
423
424 let witness = AddressWitness::P2sh {
425 signatures,
426 redeem_script: BinaryData::new(vec![0x52, 0xae]),
427 };
428
429 let encoded = bincode::encode_to_vec(&witness, config::standard()).unwrap();
431 let result: Result<(AddressWitness, usize), _> =
432 bincode::decode_from_slice(&encoded, config::standard());
433
434 assert!(
435 result.is_err(),
436 "AUDIT L1: P2SH witness with {} signatures should be rejected during \
437 deserialization. MAX_P2SH_SIGNATURES = {}.",
438 num_signatures,
439 MAX_P2SH_SIGNATURES,
440 );
441 }
442
443 #[test]
451 fn test_p2sh_witness_max_signatures_boundary() {
452 for count in [50, 100, 500] {
454 let signatures: Vec<BinaryData> = (0..count)
455 .map(|_| BinaryData::new(vec![0x30, 0x44, 0x02, 0x20]))
456 .collect();
457
458 let witness = AddressWitness::P2sh {
459 signatures,
460 redeem_script: BinaryData::new(vec![0x52, 0xae]),
461 };
462
463 let encoded = bincode::encode_to_vec(&witness, config::standard()).unwrap();
464 let result: Result<(AddressWitness, usize), _> =
465 bincode::decode_from_slice(&encoded, config::standard());
466
467 assert!(
468 result.is_err(),
469 "AUDIT L3: P2SH witness with {} signatures should be rejected during \
470 deserialization. MAX_P2SH_SIGNATURES = {}.",
471 count,
472 MAX_P2SH_SIGNATURES,
473 );
474 }
475
476 let signatures: Vec<BinaryData> = (0..MAX_P2SH_SIGNATURES)
478 .map(|_| BinaryData::new(vec![0x30, 0x44, 0x02, 0x20]))
479 .collect();
480
481 let witness = AddressWitness::P2sh {
482 signatures,
483 redeem_script: BinaryData::new(vec![0x52, 0xae]),
484 };
485
486 let encoded = bincode::encode_to_vec(&witness, config::standard()).unwrap();
487 let decoded: AddressWitness = bincode::decode_from_slice(&encoded, config::standard())
488 .unwrap()
489 .0;
490
491 assert_eq!(witness, decoded);
492
493 let signatures: Vec<BinaryData> = (0..MAX_P2SH_SIGNATURES + 1)
495 .map(|_| BinaryData::new(vec![0x30, 0x44, 0x02, 0x20]))
496 .collect();
497
498 let witness = AddressWitness::P2sh {
499 signatures,
500 redeem_script: BinaryData::new(vec![0x52, 0xae]),
501 };
502
503 let encoded = bincode::encode_to_vec(&witness, config::standard()).unwrap();
504 let result: Result<(AddressWitness, usize), _> =
505 bincode::decode_from_slice(&encoded, config::standard());
506
507 assert!(
508 result.is_err(),
509 "P2SH witness with {} signatures (MAX + 1) should be rejected",
510 MAX_P2SH_SIGNATURES + 1,
511 );
512 }
513}