dpp/address_funds/
witness.rs

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
9/// Maximum number of entries in a P2SH signatures vector.
10/// This is 16 (max keys from OP_PUSHNUM_16) + 1 (CHECKMULTISIG dummy byte).
11pub const MAX_P2SH_SIGNATURES: usize = 17;
12
13/// The input witness data required to spend from a PlatformAddress.
14///
15/// This enum captures the different spending patterns for P2PKH and P2SH addresses.
16#[derive(Debug, Clone, PartialEq, Ord, PartialOrd, Eq)]
17pub enum AddressWitness {
18    /// P2PKH witness: recoverable signature only
19    ///
20    /// Used for spending from a Pay-to-Public-Key-Hash address.
21    /// The public key is recovered from the signature during verification,
22    /// saving 33 bytes per witness compared to including the public key.
23    P2pkh {
24        /// The recoverable ECDSA signature (65 bytes with recovery byte prefix)
25        signature: BinaryData, //todo change to [u8;65]
26    },
27    /// P2SH witness: signatures + redeem script
28    ///
29    /// Used for spending from a Pay-to-Script-Hash address (e.g., multisig).
30    /// For a 2-of-3 multisig, signatures would be `[OP_0, sig1, sig2]` and
31    /// redeem_script would be `OP_2 <pub1> <pub2> <pub3> OP_3 OP_CHECKMULTISIG`.
32    P2sh {
33        /// The signatures (may include placeholder bytes like OP_0 for CHECKMULTISIG bug)
34        signatures: Vec<BinaryData>,
35        /// The redeem script that hashes to the address
36        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    /// Generates a unique identifier for this witness based on its contents.
243    ///
244    /// This is used for deduplication purposes in unique_identifiers() implementations.
245    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    /// Returns the redeem script if this is a P2SH witness
272    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    /// Returns true if this is a P2PKH witness
280    pub fn is_p2pkh(&self) -> bool {
281        matches!(self, AddressWitness::P2pkh { .. })
282    }
283
284    /// Returns true if this is a P2SH witness
285    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]),                   // OP_0 placeholder
316                BinaryData::new(vec![0x30, 0x44, 0x02, 0x20]), // sig1
317                BinaryData::new(vec![0x30, 0x45, 0x02, 0x21]), // sig2
318            ],
319            redeem_script: BinaryData::new(vec![
320                0x52, // OP_2
321                0x21, // push 33 bytes (pubkey1)
322                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, // OP_3
325                0xae, // OP_CHECKMULTISIG
326            ]),
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        // Different signature should produce different ID
349        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        // Different redeem script should produce different ID
369        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    /// AUDIT L1: Unbounded P2SH witness size during deserialization.
410    ///
411    /// The `Decode` impl for `AddressWitness::P2sh` now enforces
412    /// `MAX_P2SH_SIGNATURES` during deserialization. A payload with more
413    /// signatures than the limit is rejected with a decode error.
414    ///
415    /// Location: rs-dpp/src/address_funds/witness.rs
416    #[test]
417    fn test_p2sh_witness_rejects_excessive_signatures() {
418        // Create a P2SH witness with 1000 signatures — far above MAX_P2SH_SIGNATURES
419        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        // Encode succeeds (encoding has no limit), but decode must reject
430        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    /// AUDIT L3: No maximum length check on P2SH signatures vector.
444    ///
445    /// The deserialization now enforces `MAX_P2SH_SIGNATURES` (17). Signature
446    /// counts above this limit are rejected during decode. The boundary value
447    /// (17) is accepted, and 18+ is rejected.
448    ///
449    /// Location: rs-dpp/src/address_funds/witness.rs
450    #[test]
451    fn test_p2sh_witness_max_signatures_boundary() {
452        // Counts above MAX_P2SH_SIGNATURES should be rejected during decode
453        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        // MAX_P2SH_SIGNATURES (17) should be accepted
477        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        // MAX_P2SH_SIGNATURES + 1 should be rejected
494        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}