Skip to main content

dpp/address_funds/
platform_address.rs

1use crate::address_funds::AddressWitness;
2use crate::address_funds::AddressWitnessVerificationOperations;
3use crate::prelude::AddressNonce;
4use crate::ProtocolError;
5use bech32::{Bech32m, Hrp};
6use bincode::{Decode, DecodeUntrusted, Encode};
7use dashcore::address::Payload;
8use dashcore::blockdata::script::ScriptBuf;
9use dashcore::hashes::{sha256d, Hash};
10use dashcore::key::Secp256k1;
11use dashcore::secp256k1::ecdsa::RecoverableSignature;
12use dashcore::secp256k1::Message;
13use dashcore::signer::CompactSignature;
14use dashcore::{Address, Network, PrivateKey, PubkeyHash, PublicKey, ScriptHash};
15use platform_serialization_derive::{
16    PlatformDeserializeTrusted, PlatformDeserializeUntrusted, PlatformSerialize,
17};
18#[cfg(feature = "serde-conversion")]
19use serde::{Deserialize, Serialize};
20use std::convert::TryFrom;
21use std::str::FromStr;
22
23/// The size of the address hash (20 bytes for both P2PKH and P2SH)
24pub const ADDRESS_HASH_SIZE: usize = 20;
25
26#[derive(
27    Debug,
28    PartialEq,
29    Eq,
30    Clone,
31    Copy,
32    Hash,
33    Ord,
34    PartialOrd,
35    Encode,
36    Decode,
37    PlatformSerialize,
38    PlatformDeserializeTrusted,
39    PlatformDeserializeUntrusted,
40    DecodeUntrusted,
41)]
42#[platform_serialize(unversioned)]
43pub enum PlatformAddress {
44    /// Pay to pubkey hash
45    /// - bech32m encoding type byte: 0xb0
46    /// - storage key type byte: 0x00
47    P2pkh([u8; 20]),
48    /// Pay to script hash
49    /// - bech32m encoding type byte: 0x80
50    /// - storage key type byte: 0x01
51    P2sh([u8; 20]),
52}
53
54#[cfg(all(feature = "json-conversion", feature = "serde-conversion"))]
55impl crate::serialization::JsonConvertible for PlatformAddress {}
56
57#[cfg(all(feature = "value-conversion", feature = "serde-conversion"))]
58impl crate::serialization::ValueConvertible for PlatformAddress {}
59
60#[cfg(all(
61    test,
62    feature = "json-conversion",
63    feature = "value-conversion",
64    feature = "serde-conversion"
65))]
66mod json_convertible_tests {
67    use super::*;
68    use platform_value::Value;
69    use serde_json::json;
70
71    // `PlatformAddress` has a manual `Serialize`/`Deserialize`: it serializes
72    // as a 21-byte payload (1 type byte + 20 hash bytes), shown as a hex
73    // string in HR formats and raw bytes in non-HR. Both variants share the
74    // same wire shape — only the leading type byte differs.
75
76    #[test]
77    fn json_round_trip_p2pkh() {
78        use crate::serialization::JsonConvertible;
79        let original = PlatformAddress::P2pkh([0xab; 20]);
80        let json = original.to_json().expect("to_json");
81        // Type byte 0x00 (storage variant index for P2pkh) || 20 × 0xab
82        assert_eq!(json, json!("00abababababababababababababababababababab"));
83        let recovered = PlatformAddress::from_json(json).expect("from_json");
84        assert_eq!(original, recovered);
85    }
86
87    #[test]
88    fn json_round_trip_p2sh() {
89        use crate::serialization::JsonConvertible;
90        let original = PlatformAddress::P2sh([0xcd; 20]);
91        let json = original.to_json().expect("to_json");
92        // Type byte 0x01 (storage variant index for P2sh) || 20 × 0xcd
93        assert_eq!(json, json!("01cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd"));
94        let recovered = PlatformAddress::from_json(json).expect("from_json");
95        assert_eq!(original, recovered);
96    }
97
98    #[test]
99    fn value_round_trip_p2pkh() {
100        use crate::serialization::ValueConvertible;
101        let original = PlatformAddress::P2pkh([0xab; 20]);
102        let value = original.to_object().expect("to_object");
103        // `platform_value` is treated as non-HR by `is_human_readable()`, so
104        // the address serializes as raw bytes here.
105        let mut expected = vec![0x00];
106        expected.extend_from_slice(&[0xab; 20]);
107        assert_eq!(value, Value::Bytes(expected));
108        let recovered = PlatformAddress::from_object(value).expect("from_object");
109        assert_eq!(original, recovered);
110    }
111
112    #[test]
113    fn value_round_trip_p2sh() {
114        use crate::serialization::ValueConvertible;
115        let original = PlatformAddress::P2sh([0xcd; 20]);
116        let value = original.to_object().expect("to_object");
117        let mut expected = vec![0x01];
118        expected.extend_from_slice(&[0xcd; 20]);
119        assert_eq!(value, Value::Bytes(expected));
120        let recovered = PlatformAddress::from_object(value).expect("from_object");
121        assert_eq!(original, recovered);
122    }
123}
124
125// Custom serde impls so JSON / `platform_value` output is the canonical 21-byte
126// address representation (hex string in human-readable formats, raw bytes in
127// binary formats) — matching the wasm wrapper's serde and what consumers expect.
128// The `Encode` / `Decode` derives above are the consensus binary format and are
129// untouched.
130#[cfg(feature = "serde-conversion")]
131impl Serialize for PlatformAddress {
132    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
133    where
134        S: serde::Serializer,
135    {
136        let bytes = self.to_bytes();
137        if serializer.is_human_readable() {
138            serializer.serialize_str(&hex::encode(&bytes))
139        } else {
140            serializer.serialize_bytes(&bytes)
141        }
142    }
143}
144
145#[cfg(feature = "serde-conversion")]
146impl<'de> Deserialize<'de> for PlatformAddress {
147    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
148    where
149        D: serde::Deserializer<'de>,
150    {
151        use serde::de::{self, Visitor};
152        use std::fmt;
153
154        /// Maximum on-the-wire byte length for a `PlatformAddress`: 1 type byte + 20 hash bytes.
155        const PLATFORM_ADDRESS_BYTE_LEN: usize = 21;
156
157        struct PlatformAddressVisitor;
158
159        impl<'de> Visitor<'de> for PlatformAddressVisitor {
160            type Value = PlatformAddress;
161
162            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
163                formatter.write_str("PlatformAddress as 21 bytes or hex string")
164            }
165
166            fn visit_str<E: de::Error>(self, value: &str) -> Result<Self::Value, E> {
167                let bytes =
168                    hex::decode(value).map_err(|err| E::custom(format!("invalid hex: {}", err)))?;
169                if bytes.len() != PLATFORM_ADDRESS_BYTE_LEN {
170                    return Err(E::invalid_length(bytes.len(), &self));
171                }
172                PlatformAddress::from_bytes(&bytes).map_err(|err| E::custom(err.to_string()))
173            }
174
175            fn visit_string<E: de::Error>(self, value: String) -> Result<Self::Value, E> {
176                self.visit_str(&value)
177            }
178
179            fn visit_bytes<E: de::Error>(self, value: &[u8]) -> Result<Self::Value, E> {
180                if value.len() != PLATFORM_ADDRESS_BYTE_LEN {
181                    return Err(E::invalid_length(value.len(), &self));
182                }
183                PlatformAddress::from_bytes(value).map_err(|err| E::custom(err.to_string()))
184            }
185
186            fn visit_byte_buf<E: de::Error>(self, value: Vec<u8>) -> Result<Self::Value, E> {
187                self.visit_bytes(&value)
188            }
189
190            fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
191            where
192                A: de::SeqAccess<'de>,
193            {
194                // Cap at PLATFORM_ADDRESS_BYTE_LEN + 1 so we can detect over-long input
195                // without allocating arbitrary memory from a malicious peer.
196                let mut bytes = Vec::with_capacity(PLATFORM_ADDRESS_BYTE_LEN);
197                while let Some(byte) = seq.next_element::<u8>()? {
198                    if bytes.len() >= PLATFORM_ADDRESS_BYTE_LEN {
199                        return Err(de::Error::invalid_length(
200                            bytes.len() + 1,
201                            &"at most 21 bytes",
202                        ));
203                    }
204                    bytes.push(byte);
205                }
206                if bytes.len() != PLATFORM_ADDRESS_BYTE_LEN {
207                    return Err(de::Error::invalid_length(bytes.len(), &self));
208                }
209                PlatformAddress::from_bytes(&bytes)
210                    .map_err(|err| de::Error::custom(err.to_string()))
211            }
212        }
213
214        // Dispatch on the format's self-description: human-readable formats (JSON, TOML)
215        // get the hex-string path; binary formats get raw bytes. This avoids the
216        // `deserialize_any` pitfall on non-self-describing transports.
217        if deserializer.is_human_readable() {
218            deserializer.deserialize_str(PlatformAddressVisitor)
219        } else {
220            deserializer.deserialize_bytes(PlatformAddressVisitor)
221        }
222    }
223}
224
225impl TryFrom<Address> for PlatformAddress {
226    type Error = ProtocolError;
227
228    fn try_from(address: Address) -> Result<Self, Self::Error> {
229        match address.payload() {
230            Payload::PubkeyHash(hash) => Ok(PlatformAddress::P2pkh(*hash.as_ref())),
231            Payload::ScriptHash(hash) => Ok(PlatformAddress::P2sh(*hash.as_ref())),
232            _ => Err(ProtocolError::DecodingError(
233                "unsupported address type for PlatformAddress: only P2PKH and P2SH are supported"
234                    .to_string(),
235            )),
236        }
237    }
238}
239
240impl From<&PrivateKey> for PlatformAddress {
241    /// Derives a P2PKH Platform address from a private key.
242    ///
243    /// The address is derived as: P2PKH(Hash160(compressed_public_key))
244    /// where Hash160 = RIPEMD160(SHA256(x)), which is the standard Bitcoin P2PKH derivation.
245    fn from(private_key: &PrivateKey) -> Self {
246        let secp = Secp256k1::new();
247        let pubkey_hash = private_key.public_key(&secp).pubkey_hash();
248        PlatformAddress::P2pkh(*pubkey_hash.as_byte_array())
249    }
250}
251
252impl Default for PlatformAddress {
253    fn default() -> Self {
254        PlatformAddress::P2pkh([0u8; 20])
255    }
256}
257
258/// Human-readable part for Platform addresses on mainnet (DIP-0018)
259pub const PLATFORM_HRP_MAINNET: &str = "dash";
260/// Human-readable part for Platform addresses on testnet/devnet/regtest (DIP-0018)
261pub const PLATFORM_HRP_TESTNET: &str = "tdash";
262
263/// Validates an already-lowercased HRP and returns whether it is mainnet.
264///
265/// `true` = mainnet (`dash`), `false` = non-mainnet (`tdash`).
266/// Returns an error for any other value.
267pub(crate) fn classify_platform_hrp(hrp: &str) -> Result<bool, ProtocolError> {
268    match hrp {
269        PLATFORM_HRP_MAINNET => Ok(true),
270        PLATFORM_HRP_TESTNET => Ok(false),
271        other => Err(ProtocolError::DecodingError(format!(
272            "not a platform address: HRP '{other}' is neither \
273             '{PLATFORM_HRP_MAINNET}' nor '{PLATFORM_HRP_TESTNET}'"
274        ))),
275    }
276}
277
278impl PlatformAddress {
279    /// Type byte for P2PKH addresses in bech32m encoding (user-facing)
280    pub const P2PKH_TYPE: u8 = 0xb0;
281    /// Type byte for P2SH addresses in bech32m encoding (user-facing)
282    pub const P2SH_TYPE: u8 = 0x80;
283
284    /// Returns the appropriate HRP (Human-Readable Part) for the given network.
285    ///
286    /// Per DIP-0018:
287    /// - Mainnet: "dash"
288    /// - Testnet/Devnet/Regtest: "tdash"
289    pub fn hrp_for_network(network: Network) -> &'static str {
290        match network {
291            Network::Mainnet => PLATFORM_HRP_MAINNET,
292            Network::Testnet | Network::Devnet | Network::Regtest => PLATFORM_HRP_TESTNET,
293        }
294    }
295
296    /// Encodes the PlatformAddress as a bech32m string for the specified network.
297    ///
298    /// The encoding follows DIP-0018:
299    /// - Format: `<HRP>1<data-part>`
300    /// - Data: type_byte (0xb0 for P2PKH, 0x80 for P2SH) || 20-byte hash
301    /// - Checksum: bech32m (BIP-350)
302    ///
303    /// NOTE: This uses bech32m type bytes (0xb0/0x80) for user-facing addresses,
304    /// NOT the storage type bytes (0x00/0x01) used in GroveDB keys.
305    ///
306    /// # Example
307    /// ```ignore
308    /// let address = PlatformAddress::P2pkh([0xf7, 0xda, ...]);
309    /// let encoded = address.to_bech32m_string(Network::Mainnet);
310    /// // Returns something like "dash1k..."
311    /// ```
312    pub fn to_bech32m_string(&self, network: Network) -> String {
313        let hrp_str = Self::hrp_for_network(network);
314        let hrp = Hrp::parse(hrp_str).expect("HRP is valid");
315
316        // Build the 21-byte payload: type_byte || hash
317        // Using bech32m type bytes (0xb0/0x80), NOT storage type bytes (0x00/0x01)
318        let mut payload = Vec::with_capacity(1 + ADDRESS_HASH_SIZE);
319        match self {
320            PlatformAddress::P2pkh(hash) => {
321                payload.push(Self::P2PKH_TYPE);
322                payload.extend_from_slice(hash);
323            }
324            PlatformAddress::P2sh(hash) => {
325                payload.push(Self::P2SH_TYPE);
326                payload.extend_from_slice(hash);
327            }
328        }
329
330        // Verified that this can not error
331        bech32::encode::<Bech32m>(hrp, &payload).expect("encoding should succeed")
332    }
333
334    /// Decodes a bech32m-encoded Platform address string per DIP-0018.
335    ///
336    /// Accepts both `dash` (mainnet) and `tdash` (non-mainnet) HRPs.
337    /// The address is network-agnostic; callers that need a network guard should
338    /// use [`is_mainnet_bech32m`](Self::is_mainnet_bech32m) before decoding.
339    ///
340    /// # Returns
341    /// - `Ok(PlatformAddress)` - The decoded address
342    /// - `Err(ProtocolError)` - If the string is malformed or its HRP is not a
343    ///   recognized platform HRP
344    pub fn from_bech32m_string(s: &str) -> Result<Self, ProtocolError> {
345        let (hrp, data) =
346            bech32::decode(s).map_err(|e| ProtocolError::DecodingError(format!("{}", e)))?;
347
348        classify_platform_hrp(&hrp.as_str().to_ascii_lowercase())?;
349
350        // Validate payload length: 1 type byte + 20 hash bytes = 21 bytes
351        if data.len() != 1 + ADDRESS_HASH_SIZE {
352            return Err(ProtocolError::DecodingError(format!(
353                "invalid Platform address length: expected {} bytes, got {}",
354                1 + ADDRESS_HASH_SIZE,
355                data.len()
356            )));
357        }
358
359        // Parse using bech32m type bytes (0xb0/0x80), NOT storage type bytes
360        let address_type = data[0];
361        let hash: [u8; 20] = data[1..21]
362            .try_into()
363            .map_err(|_| ProtocolError::DecodingError("invalid hash length".to_string()))?;
364
365        let address = match address_type {
366            Self::P2PKH_TYPE => Ok(PlatformAddress::P2pkh(hash)),
367            Self::P2SH_TYPE => Ok(PlatformAddress::P2sh(hash)),
368            _ => Err(ProtocolError::DecodingError(format!(
369                "invalid address type: 0x{:02x}",
370                address_type
371            ))),
372        }?;
373
374        Ok(address)
375    }
376
377    /// Classifies a bech32m platform-address string as mainnet or non-mainnet.
378    ///
379    /// Fully decodes `s` (validating checksum and data part) then classifies
380    /// the HRP: `dash` means mainnet, `tdash` means non-mainnet (Testnet /
381    /// Devnet / Regtest — these are indistinguishable by HRP alone per DIP-0018).
382    ///
383    /// # Returns
384    /// - `Ok(true)` - mainnet (`dash` HRP)
385    /// - `Ok(false)` - non-mainnet (`tdash` HRP: Testnet/Devnet/Regtest)
386    /// - `Err(ProtocolError)` - malformed address or non-platform HRP
387    pub fn is_mainnet_bech32m(s: &str) -> Result<bool, ProtocolError> {
388        let (hrp, _) =
389            bech32::decode(s).map_err(|e| ProtocolError::DecodingError(format!("{e}")))?;
390        classify_platform_hrp(&hrp.to_lowercase())
391    }
392
393    /// Converts the PlatformAddress to a dashcore Address with the specified network.
394    pub fn to_address_with_network(&self, network: Network) -> Address {
395        match self {
396            PlatformAddress::P2pkh(hash) => Address::new(
397                network,
398                Payload::PubkeyHash(PubkeyHash::from_byte_array(*hash)),
399            ),
400            PlatformAddress::P2sh(hash) => Address::new(
401                network,
402                Payload::ScriptHash(ScriptHash::from_byte_array(*hash)),
403            ),
404        }
405    }
406
407    /// Converts the PlatformAddress to bytes for storage keys.
408    /// Format: [variant_index (1 byte)] + [hash (20 bytes)]
409    ///
410    /// Uses bincode serialization which produces: 0x00 for P2pkh, 0x01 for P2sh.
411    /// These bytes are used as keys in GroveDB.
412    pub fn to_bytes(&self) -> Vec<u8> {
413        bincode::encode_to_vec(self, bincode::config::standard())
414            .expect("PlatformAddress serialization cannot fail")
415    }
416
417    /// Gets a base64 string of the PlatformAddress concatenated with the nonce.
418    /// This creates a unique identifier for address-based state transition inputs.
419    pub fn base64_string_with_nonce(&self, nonce: AddressNonce) -> String {
420        use base64::engine::general_purpose::STANDARD;
421        use base64::Engine;
422
423        let mut bytes = self.to_bytes();
424        bytes.extend_from_slice(&nonce.to_be_bytes());
425
426        STANDARD.encode(bytes)
427    }
428
429    /// Creates a PlatformAddress from storage bytes.
430    /// Format: [variant_index (1 byte)] + [hash (20 bytes)]
431    ///
432    /// Uses bincode deserialization which expects: 0x00 for P2pkh, 0x01 for P2sh.
433    pub fn from_bytes(bytes: &[u8]) -> Result<Self, ProtocolError> {
434        let (address, _): (Self, usize) =
435            bincode::decode_from_slice_untrusted(bytes, bincode::config::standard()).map_err(
436                |e| ProtocolError::DecodingError(format!("cannot decode PlatformAddress: {}", e)),
437            )?;
438        Ok(address)
439    }
440
441    /// Returns the hash portion of the address (20 bytes)
442    pub fn hash(&self) -> &[u8; 20] {
443        match self {
444            PlatformAddress::P2pkh(hash) => hash,
445            PlatformAddress::P2sh(hash) => hash,
446        }
447    }
448
449    /// Returns true if this is a P2PKH address
450    pub fn is_p2pkh(&self) -> bool {
451        matches!(self, PlatformAddress::P2pkh(_))
452    }
453
454    /// Returns true if this is a P2SH address
455    pub fn is_p2sh(&self) -> bool {
456        matches!(self, PlatformAddress::P2sh(_))
457    }
458
459    /// Verifies that the provided witness matches this address and that signatures are valid.
460    ///
461    /// For P2PKH addresses:
462    /// - The witness must be `AddressWitness::P2pkh`
463    /// - The public key must hash to this address
464    /// - The signature must be valid for the signable bytes
465    ///
466    /// For P2SH addresses:
467    /// - The witness must be `AddressWitness::P2sh`
468    /// - The redeem script must hash to this address
469    /// - For multisig scripts: M valid signatures must be provided for the signable bytes
470    ///
471    /// # Arguments
472    /// * `witness` - The witness containing signature(s) and either a public key (P2PKH) or redeem script (P2SH)
473    /// * `signable_bytes` - The data that was signed (will be double-SHA256 hashed internally)
474    ///
475    /// # Returns
476    /// * `Ok(AddressWitnessVerificationOperations)` - Operations performed if verification succeeds
477    /// * `Err(ProtocolError)` if verification fails
478    pub fn verify_bytes_against_witness(
479        &self,
480        witness: &AddressWitness,
481        signable_bytes: &[u8],
482    ) -> Result<AddressWitnessVerificationOperations, ProtocolError> {
483        match (self, witness) {
484            (PlatformAddress::P2pkh(pubkey_hash), AddressWitness::P2pkh { signature }) => {
485                // Use verify_hash_signature which:
486                // 1. Computes double_sha256(signable_bytes)
487                // 2. Recovers the public key from the signature
488                // 3. Verifies Hash160(recovered_pubkey) matches pubkey_hash
489                //
490                // This saves 33 bytes per witness (no need to include pubkey)
491                // at a ~4% CPU cost increase (recovery vs verify).
492                let data_hash = dashcore::signer::double_sha(signable_bytes);
493                dashcore::signer::verify_hash_signature(
494                    &data_hash,
495                    signature.as_slice(),
496                    pubkey_hash,
497                )
498                .map_err(|e| {
499                    ProtocolError::AddressWitnessError(format!(
500                        "P2PKH signature verification failed: {}",
501                        e
502                    ))
503                })?;
504
505                Ok(AddressWitnessVerificationOperations::for_p2pkh(
506                    signable_bytes.len(),
507                ))
508            }
509            (
510                PlatformAddress::P2sh(script_hash),
511                AddressWitness::P2sh {
512                    signatures,
513                    redeem_script,
514                },
515            ) => {
516                // First verify the redeem script hashes to the address
517                let script = ScriptBuf::from_bytes(redeem_script.to_vec());
518                let computed_hash = script.script_hash();
519                if computed_hash.as_byte_array() != script_hash {
520                    return Err(ProtocolError::AddressWitnessError(format!(
521                        "Script hash {} does not match address hash {}",
522                        hex::encode(computed_hash.as_byte_array()),
523                        hex::encode(script_hash)
524                    )));
525                }
526
527                // Parse the redeem script to extract public keys and threshold
528                // Expected format for multisig: OP_M <pubkey1> <pubkey2> ... <pubkeyN> OP_N OP_CHECKMULTISIG
529                let (threshold, pubkeys) = Self::parse_multisig_script(&script)?;
530
531                // Filter out empty signatures (OP_0 placeholders for CHECKMULTISIG bug)
532                let valid_signatures: Vec<_> = signatures
533                    .iter()
534                    .filter(|sig| !sig.is_empty() && sig.as_slice() != [0x00])
535                    .collect();
536
537                if valid_signatures.len() < threshold {
538                    return Err(ProtocolError::AddressWitnessError(format!(
539                        "Not enough signatures: got {}, need {}",
540                        valid_signatures.len(),
541                        threshold
542                    )));
543                }
544
545                // Verify signatures against public keys
546                // In standard multisig, signatures must match public keys in order
547                let mut sig_idx = 0;
548                let mut pubkey_idx = 0;
549                let mut matched = 0;
550                let mut signature_verifications: u16 = 0;
551
552                let signable_bytes_hash = sha256d::Hash::hash(signable_bytes).to_byte_array();
553                let msg = Message::from_digest(signable_bytes_hash);
554                let secp = Secp256k1::new();
555
556                while sig_idx < valid_signatures.len() && pubkey_idx < pubkeys.len() {
557                    signature_verifications += 1;
558
559                    let sig = RecoverableSignature::from_compact_signature(
560                        valid_signatures[sig_idx].as_slice(),
561                    )
562                    .map_err(|e| {
563                        ProtocolError::AddressWitnessError(format!(
564                            "Invalid signature format: {}",
565                            e
566                        ))
567                    })?;
568
569                    let pub_key = PublicKey::from_slice(&pubkeys[pubkey_idx]).map_err(|e| {
570                        ProtocolError::AddressWitnessError(format!("Invalid public key: {}", e))
571                    })?;
572
573                    if secp
574                        .verify_ecdsa(&msg, &sig.to_standard(), &pub_key.inner)
575                        .is_ok()
576                    {
577                        matched += 1;
578                        sig_idx += 1;
579                    }
580                    pubkey_idx += 1;
581                }
582
583                if matched >= threshold {
584                    Ok(AddressWitnessVerificationOperations::for_p2sh_multisig(
585                        signature_verifications,
586                        signable_bytes.len(),
587                    ))
588                } else {
589                    Err(ProtocolError::AddressWitnessError(format!(
590                        "Not enough valid signatures: verified {}, need {}",
591                        matched, threshold
592                    )))
593                }
594            }
595            (PlatformAddress::P2pkh(_), AddressWitness::P2sh { .. }) => {
596                Err(ProtocolError::AddressWitnessError(
597                    "P2PKH address requires P2pkh witness, got P2sh".to_string(),
598                ))
599            }
600            (PlatformAddress::P2sh(_), AddressWitness::P2pkh { .. }) => {
601                Err(ProtocolError::AddressWitnessError(
602                    "P2SH address requires P2sh witness, got P2pkh".to_string(),
603                ))
604            }
605        }
606    }
607
608    /// Parses a multisig redeem script and extracts the threshold (M) and public keys.
609    ///
610    /// Expected format: OP_M <pubkey1> <pubkey2> ... <pubkeyN> OP_N OP_CHECKMULTISIG
611    ///
612    /// # Supported Scripts
613    ///
614    /// Currently only standard bare multisig scripts are supported. Other P2SH script types
615    /// (timelocks, hash puzzles, custom scripts) are not supported and will return an error.
616    ///
617    /// Full script execution would require either:
618    /// - Using the `bitcoinconsensus` library with a synthetic spending transaction
619    /// - Implementing a complete script interpreter
620    ///
621    /// For Platform's authorization use cases, multisig is the primary expected P2SH pattern.
622    fn parse_multisig_script(script: &ScriptBuf) -> Result<(usize, Vec<Vec<u8>>), ProtocolError> {
623        use dashcore::blockdata::opcodes::all::*;
624
625        let mut instructions = script.instructions();
626        let mut pubkeys = Vec::new();
627
628        // First instruction should be OP_M (threshold)
629        let threshold = match instructions.next() {
630            Some(Ok(dashcore::blockdata::script::Instruction::Op(op))) => {
631                let byte = op.to_u8();
632                if byte >= OP_PUSHNUM_1.to_u8() && byte <= OP_PUSHNUM_16.to_u8() {
633                    (byte - OP_PUSHNUM_1.to_u8() + 1) as usize
634                } else {
635                    return Err(ProtocolError::AddressWitnessError(format!(
636                        "Unsupported P2SH script type: only standard multisig (OP_M ... OP_N OP_CHECKMULTISIG) is supported. \
637                         First opcode was 0x{:02x}, expected OP_1 through OP_16",
638                        byte
639                    )));
640                }
641            }
642            Some(Ok(dashcore::blockdata::script::Instruction::PushBytes(_))) => {
643                return Err(ProtocolError::AddressWitnessError(
644                    "Unsupported P2SH script type: only standard multisig is supported. \
645                     Script starts with a data push instead of OP_M threshold."
646                        .to_string(),
647                ))
648            }
649            Some(Err(e)) => {
650                return Err(ProtocolError::AddressWitnessError(format!(
651                    "Error parsing P2SH script: {:?}",
652                    e
653                )))
654            }
655            None => {
656                return Err(ProtocolError::AddressWitnessError(
657                    "Empty P2SH redeem script".to_string(),
658                ))
659            }
660        };
661
662        // Read public keys until we hit OP_N
663        loop {
664            match instructions.next() {
665                Some(Ok(dashcore::blockdata::script::Instruction::PushBytes(bytes))) => {
666                    // Only compressed public keys (33 bytes) are allowed
667                    let len = bytes.len();
668                    if len != 33 {
669                        return Err(ProtocolError::UncompressedPublicKeyNotAllowedError(
670                            crate::consensus::signature::UncompressedPublicKeyNotAllowedError::new(
671                                len,
672                            ),
673                        ));
674                    }
675                    pubkeys.push(bytes.as_bytes().to_vec());
676                }
677                Some(Ok(dashcore::blockdata::script::Instruction::Op(op))) => {
678                    let byte = op.to_u8();
679                    if byte >= OP_PUSHNUM_1.to_u8() && byte <= OP_PUSHNUM_16.to_u8() {
680                        // This is OP_N, the total number of keys
681                        let n = (byte - OP_PUSHNUM_1.to_u8() + 1) as usize;
682                        if pubkeys.len() != n {
683                            return Err(ProtocolError::AddressWitnessError(format!(
684                                "Multisig script declares {} keys but contains {}",
685                                n,
686                                pubkeys.len()
687                            )));
688                        }
689                        break;
690                    } else if op == OP_CHECKMULTISIG || op == OP_CHECKMULTISIGVERIFY {
691                        // Hit CHECKMULTISIG without seeing OP_N - malformed
692                        return Err(ProtocolError::AddressWitnessError(
693                            "Malformed multisig script: OP_CHECKMULTISIG before OP_N".to_string(),
694                        ));
695                    } else {
696                        return Err(ProtocolError::AddressWitnessError(format!(
697                            "Unsupported opcode 0x{:02x} in P2SH script. Only standard multisig is supported.",
698                            byte
699                        )));
700                    }
701                }
702                Some(Err(e)) => {
703                    return Err(ProtocolError::AddressWitnessError(format!(
704                        "Error parsing multisig script: {:?}",
705                        e
706                    )))
707                }
708                None => {
709                    return Err(ProtocolError::AddressWitnessError(
710                        "Incomplete multisig script: unexpected end before OP_N".to_string(),
711                    ))
712                }
713            }
714        }
715
716        // Validate threshold
717        if threshold > pubkeys.len() {
718            return Err(ProtocolError::AddressWitnessError(format!(
719                "Invalid multisig: threshold {} exceeds number of keys {}",
720                threshold,
721                pubkeys.len()
722            )));
723        }
724
725        // Next should be OP_CHECKMULTISIG
726        match instructions.next() {
727            Some(Ok(dashcore::blockdata::script::Instruction::Op(op))) => {
728                if op == OP_CHECKMULTISIG {
729                    // Standard multisig - verify script is complete
730                    if instructions.next().is_some() {
731                        return Err(ProtocolError::AddressWitnessError(
732                            "Multisig script has extra data after OP_CHECKMULTISIG".to_string(),
733                        ));
734                    }
735                    Ok((threshold, pubkeys))
736                } else if op == OP_CHECKMULTISIGVERIFY {
737                    Err(ProtocolError::AddressWitnessError(
738                        "OP_CHECKMULTISIGVERIFY is not supported, only OP_CHECKMULTISIG"
739                            .to_string(),
740                    ))
741                } else {
742                    Err(ProtocolError::AddressWitnessError(format!(
743                        "Expected OP_CHECKMULTISIG, got opcode 0x{:02x}",
744                        op.to_u8()
745                    )))
746                }
747            }
748            _ => Err(ProtocolError::AddressWitnessError(
749                "Invalid multisig script: expected OP_CHECKMULTISIG after OP_N".to_string(),
750            )),
751        }
752    }
753}
754
755impl std::fmt::Display for PlatformAddress {
756    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
757        match self {
758            PlatformAddress::P2pkh(hash) => write!(f, "P2PKH({})", hex::encode(hash)),
759            PlatformAddress::P2sh(hash) => write!(f, "P2SH({})", hex::encode(hash)),
760        }
761    }
762}
763
764/// Error type for parsing a bech32m-encoded Platform address
765#[derive(Debug, Clone, PartialEq, Eq)]
766pub struct PlatformAddressParseError(pub String);
767
768impl std::fmt::Display for PlatformAddressParseError {
769    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
770        write!(f, "{}", self.0)
771    }
772}
773
774impl std::error::Error for PlatformAddressParseError {}
775
776impl FromStr for PlatformAddress {
777    type Err = PlatformAddressParseError;
778
779    /// Parses a bech32m-encoded Platform address string.
780    ///
781    /// This accepts addresses with either mainnet ("dash") or testnet ("tdash") HRP.
782    ///
783    /// # Example
784    /// ```ignore
785    /// let address: PlatformAddress = "dash1k...".parse()?;
786    /// ```
787    fn from_str(s: &str) -> Result<Self, Self::Err> {
788        Self::from_bech32m_string(s).map_err(|e| PlatformAddressParseError(e.to_string()))
789    }
790}
791
792#[cfg(test)]
793mod tests {
794    use super::*;
795    use dashcore::blockdata::opcodes::all::*;
796    use dashcore::hashes::Hash;
797    use dashcore::secp256k1::{PublicKey as RawPublicKey, Secp256k1, SecretKey as RawSecretKey};
798    use dashcore::PublicKey;
799    use platform_value::BinaryData;
800
801    /// All non-mainnet networks share the `tdash` HRP (DIP-0018), so an address parsed from a
802    /// bech32m string can only ever report Mainnet or Testnet — never Devnet/Regtest. Network
803    /// checks against a parsed address MUST therefore compare HRPs, not raw `Network` values
804    /// (`PlatformWallet::shielded_unshield_to` relies on this; comparing raw networks made every
805    /// unshield fail on devnet wallets).
806    #[test]
807    fn hrp_is_shared_across_all_non_mainnet_networks() {
808        let testnet = PlatformAddress::hrp_for_network(Network::Testnet);
809        assert_eq!(testnet, PLATFORM_HRP_TESTNET);
810        assert_eq!(PlatformAddress::hrp_for_network(Network::Devnet), testnet);
811        assert_eq!(PlatformAddress::hrp_for_network(Network::Regtest), testnet);
812        assert_ne!(
813            PlatformAddress::hrp_for_network(Network::Mainnet),
814            testnet,
815            "mainnet must use a distinct HRP"
816        );
817    }
818
819    /// Helper to create a keypair from a 32-byte seed
820    fn create_keypair(seed: [u8; 32]) -> (RawSecretKey, PublicKey) {
821        let secp = Secp256k1::new();
822        let secret_key = RawSecretKey::from_byte_array(&seed).expect("valid secret key");
823        let raw_public_key = RawPublicKey::from_secret_key(&secp, &secret_key);
824        let public_key = PublicKey::new(raw_public_key);
825        (secret_key, public_key)
826    }
827
828    /// Helper to sign data with a secret key
829    fn sign_data(data: &[u8], secret_key: &RawSecretKey) -> Vec<u8> {
830        dashcore::signer::sign(data, secret_key.as_ref())
831            .expect("signing should succeed")
832            .to_vec()
833    }
834
835    /// Creates a standard multisig redeem script: OP_M <pubkey1> ... <pubkeyN> OP_N OP_CHECKMULTISIG
836    fn create_multisig_script(threshold: u8, pubkeys: &[PublicKey]) -> Vec<u8> {
837        let mut script = Vec::new();
838
839        // OP_M (threshold)
840        script.push(OP_PUSHNUM_1.to_u8() + threshold - 1);
841
842        // Push each public key (33 bytes each for compressed)
843        for pubkey in pubkeys {
844            let bytes = pubkey.to_bytes();
845            script.push(bytes.len() as u8); // push length
846            script.extend_from_slice(&bytes);
847        }
848
849        // OP_N (total keys)
850        script.push(OP_PUSHNUM_1.to_u8() + pubkeys.len() as u8 - 1);
851
852        // OP_CHECKMULTISIG
853        script.push(OP_CHECKMULTISIG.to_u8());
854
855        script
856    }
857
858    #[test]
859    fn test_platform_address_from_private_key() {
860        // Create a keypair
861        let seed = [1u8; 32];
862        let (secret_key, public_key) = create_keypair(seed);
863
864        // Create PrivateKey from the secret key
865        let private_key = PrivateKey::new(secret_key, Network::Testnet);
866
867        // Derive address using From<&PrivateKey>
868        let address_from_private = PlatformAddress::from(&private_key);
869
870        // Derive address manually using pubkey_hash() which computes Hash160(pubkey)
871        // Hash160 = RIPEMD160(SHA256(x)), the standard Bitcoin P2PKH derivation
872        let pubkey_hash = public_key.pubkey_hash();
873        let address_from_pubkey = PlatformAddress::P2pkh(*pubkey_hash.as_byte_array());
874
875        // Both addresses should be identical
876        assert_eq!(
877            address_from_private, address_from_pubkey,
878            "Address derived from private key should match Hash160(compressed_pubkey)"
879        );
880
881        // Verify it's a P2PKH address
882        assert!(address_from_private.is_p2pkh());
883    }
884
885    #[test]
886    fn test_p2pkh_verify_signature_success() {
887        // Create a keypair
888        let seed = [1u8; 32];
889        let (secret_key, public_key) = create_keypair(seed);
890
891        // Create P2PKH address from public key hash
892        let pubkey_hash = public_key.pubkey_hash();
893        let address = PlatformAddress::P2pkh(*pubkey_hash.as_byte_array());
894
895        // Data to sign
896        let signable_bytes = b"test message for P2PKH verification";
897
898        // Sign the data
899        let signature = sign_data(signable_bytes, &secret_key);
900
901        // Create witness (only signature needed - public key is recovered)
902        let witness = AddressWitness::P2pkh {
903            signature: BinaryData::new(signature),
904        };
905
906        // Verify should succeed
907        let result = address.verify_bytes_against_witness(&witness, signable_bytes);
908        assert!(
909            result.is_ok(),
910            "P2PKH verification should succeed: {:?}",
911            result
912        );
913    }
914
915    #[test]
916    fn test_p2pkh_verify_wrong_signature_fails() {
917        // Create a keypair
918        let seed = [1u8; 32];
919        let (secret_key, public_key) = create_keypair(seed);
920
921        // Create P2PKH address from public key hash
922        let pubkey_hash = public_key.pubkey_hash();
923        let address = PlatformAddress::P2pkh(*pubkey_hash.as_byte_array());
924
925        // Sign different data than what we verify
926        let sign_bytes = b"original message";
927        let verify_bytes = b"different message";
928        let signature = sign_data(sign_bytes, &secret_key);
929
930        // Create witness with signature for different data
931        let witness = AddressWitness::P2pkh {
932            signature: BinaryData::new(signature),
933        };
934
935        // Verify should fail (recovered pubkey won't match because message differs)
936        let result = address.verify_bytes_against_witness(&witness, verify_bytes);
937        assert!(
938            result.is_err(),
939            "P2PKH verification should fail with wrong data"
940        );
941    }
942
943    #[test]
944    fn test_p2pkh_verify_wrong_key_fails() {
945        // Create two keypairs
946        let seed1 = [1u8; 32];
947        let seed2 = [2u8; 32];
948        let (_secret_key1, public_key1) = create_keypair(seed1);
949        let (secret_key2, _public_key2) = create_keypair(seed2);
950
951        // Create P2PKH address from public key 1's hash
952        let pubkey_hash = public_key1.pubkey_hash();
953        let address = PlatformAddress::P2pkh(*pubkey_hash.as_byte_array());
954
955        // Sign with key 2 (wrong key)
956        let signable_bytes = b"test message";
957        let signature = sign_data(signable_bytes, &secret_key2);
958
959        // Create witness (signature is from key 2, but address is for key 1)
960        let witness = AddressWitness::P2pkh {
961            signature: BinaryData::new(signature),
962        };
963
964        // Verify should fail (recovered pubkey hash won't match address)
965        let result = address.verify_bytes_against_witness(&witness, signable_bytes);
966        assert!(
967            result.is_err(),
968            "P2PKH verification should fail when signed with wrong key"
969        );
970    }
971
972    // NOTE: test_uncompressed_public_key_rejected was removed because P2PKH witnesses
973    // no longer include the public key - it's recovered from the signature during verification.
974    // ECDSA recovery always produces a compressed public key (33 bytes).
975
976    #[test]
977    fn test_p2sh_2_of_3_multisig_verify_success() {
978        // Create 3 keypairs for 2-of-3 multisig
979        let seeds: [[u8; 32]; 3] = [[1u8; 32], [2u8; 32], [3u8; 32]];
980        let keypairs: Vec<_> = seeds.iter().map(|s| create_keypair(*s)).collect();
981        let pubkeys: Vec<_> = keypairs.iter().map(|(_, pk)| *pk).collect();
982
983        // Create 2-of-3 multisig redeem script
984        let redeem_script = create_multisig_script(2, &pubkeys);
985
986        // Create P2SH address from script hash
987        let script_buf = ScriptBuf::from_bytes(redeem_script.clone());
988        let script_hash = script_buf.script_hash();
989        let address = PlatformAddress::P2sh(*script_hash.as_byte_array());
990
991        // Data to sign
992        let signable_bytes = b"test message for P2SH 2-of-3 multisig";
993
994        // Sign with first two keys (keys 0 and 1)
995        let sig0 = sign_data(signable_bytes, &keypairs[0].0);
996        let sig1 = sign_data(signable_bytes, &keypairs[1].0);
997
998        // Create witness with signatures in order
999        // Note: CHECKMULTISIG requires signatures in the same order as pubkeys
1000        let witness = AddressWitness::P2sh {
1001            signatures: vec![BinaryData::new(sig0), BinaryData::new(sig1)],
1002            redeem_script: BinaryData::new(redeem_script),
1003        };
1004
1005        // Verify should succeed
1006        let result = address.verify_bytes_against_witness(&witness, signable_bytes);
1007        assert!(
1008            result.is_ok(),
1009            "P2SH 2-of-3 multisig verification should succeed: {:?}",
1010            result
1011        );
1012    }
1013
1014    #[test]
1015    fn test_p2sh_2_of_3_multisig_with_keys_1_and_2_success() {
1016        // Create 3 keypairs for 2-of-3 multisig
1017        let seeds: [[u8; 32]; 3] = [[1u8; 32], [2u8; 32], [3u8; 32]];
1018        let keypairs: Vec<_> = seeds.iter().map(|s| create_keypair(*s)).collect();
1019        let pubkeys: Vec<_> = keypairs.iter().map(|(_, pk)| *pk).collect();
1020
1021        // Create 2-of-3 multisig redeem script
1022        let redeem_script = create_multisig_script(2, &pubkeys);
1023
1024        // Create P2SH address from script hash
1025        let script_buf = ScriptBuf::from_bytes(redeem_script.clone());
1026        let script_hash = script_buf.script_hash();
1027        let address = PlatformAddress::P2sh(*script_hash.as_byte_array());
1028
1029        // Data to sign
1030        let signable_bytes = b"test message for P2SH 2-of-3 multisig";
1031
1032        // Sign with keys 1 and 2 (different combination)
1033        let sig1 = sign_data(signable_bytes, &keypairs[1].0);
1034        let sig2 = sign_data(signable_bytes, &keypairs[2].0);
1035
1036        // Create witness with signatures in order
1037        let witness = AddressWitness::P2sh {
1038            signatures: vec![BinaryData::new(sig1), BinaryData::new(sig2)],
1039            redeem_script: BinaryData::new(redeem_script),
1040        };
1041
1042        // Verify should succeed
1043        let result = address.verify_bytes_against_witness(&witness, signable_bytes);
1044        assert!(
1045            result.is_ok(),
1046            "P2SH 2-of-3 multisig with keys 1 and 2 should succeed: {:?}",
1047            result
1048        );
1049    }
1050
1051    #[test]
1052    fn test_p2sh_not_enough_signatures_fails() {
1053        // Create 3 keypairs for 2-of-3 multisig
1054        let seeds: [[u8; 32]; 3] = [[1u8; 32], [2u8; 32], [3u8; 32]];
1055        let keypairs: Vec<_> = seeds.iter().map(|s| create_keypair(*s)).collect();
1056        let pubkeys: Vec<_> = keypairs.iter().map(|(_, pk)| *pk).collect();
1057
1058        // Create 2-of-3 multisig redeem script
1059        let redeem_script = create_multisig_script(2, &pubkeys);
1060
1061        // Create P2SH address from script hash
1062        let script_buf = ScriptBuf::from_bytes(redeem_script.clone());
1063        let script_hash = script_buf.script_hash();
1064        let address = PlatformAddress::P2sh(*script_hash.as_byte_array());
1065
1066        // Data to sign
1067        let signable_bytes = b"test message";
1068
1069        // Only sign with one key (need 2)
1070        let sig0 = sign_data(signable_bytes, &keypairs[0].0);
1071
1072        // Create witness with only one signature
1073        let witness = AddressWitness::P2sh {
1074            signatures: vec![BinaryData::new(sig0)],
1075            redeem_script: BinaryData::new(redeem_script),
1076        };
1077
1078        // Verify should fail
1079        let result = address.verify_bytes_against_witness(&witness, signable_bytes);
1080        assert!(
1081            result.is_err(),
1082            "P2SH should fail with only 1 signature when 2 required"
1083        );
1084        assert!(
1085            result.unwrap_err().to_string().contains("Not enough"),
1086            "Error should mention not enough signatures"
1087        );
1088    }
1089
1090    #[test]
1091    fn test_p2sh_wrong_script_hash_fails() {
1092        // Create 3 keypairs
1093        let seeds: [[u8; 32]; 3] = [[1u8; 32], [2u8; 32], [3u8; 32]];
1094        let keypairs: Vec<_> = seeds.iter().map(|s| create_keypair(*s)).collect();
1095        let pubkeys: Vec<_> = keypairs.iter().map(|(_, pk)| *pk).collect();
1096
1097        // Create a redeem script
1098        let redeem_script = create_multisig_script(2, &pubkeys);
1099
1100        // Create P2SH address with DIFFERENT hash (wrong address)
1101        let wrong_hash = [0xABu8; 20];
1102        let address = PlatformAddress::P2sh(wrong_hash);
1103
1104        // Data to sign
1105        let signable_bytes = b"test message";
1106
1107        // Sign correctly
1108        let sig0 = sign_data(signable_bytes, &keypairs[0].0);
1109        let sig1 = sign_data(signable_bytes, &keypairs[1].0);
1110
1111        // Create witness
1112        let witness = AddressWitness::P2sh {
1113            signatures: vec![BinaryData::new(sig0), BinaryData::new(sig1)],
1114            redeem_script: BinaryData::new(redeem_script),
1115        };
1116
1117        // Verify should fail (script doesn't hash to address)
1118        let result = address.verify_bytes_against_witness(&witness, signable_bytes);
1119        assert!(
1120            result.is_err(),
1121            "P2SH should fail when script hash doesn't match address"
1122        );
1123        assert!(
1124            result
1125                .unwrap_err()
1126                .to_string()
1127                .contains("does not match address hash"),
1128            "Error should mention hash mismatch"
1129        );
1130    }
1131
1132    #[test]
1133    fn test_p2pkh_and_p2sh_together() {
1134        // This test simulates having both a P2PKH and P2SH output and redeeming both
1135
1136        // === P2PKH Output ===
1137        let p2pkh_seed = [10u8; 32];
1138        let (p2pkh_secret, p2pkh_pubkey) = create_keypair(p2pkh_seed);
1139        let p2pkh_hash = p2pkh_pubkey.pubkey_hash();
1140        let p2pkh_address = PlatformAddress::P2pkh(*p2pkh_hash.as_byte_array());
1141
1142        // === P2SH Output (2-of-3 multisig) ===
1143        let p2sh_seeds: [[u8; 32]; 3] = [[20u8; 32], [21u8; 32], [22u8; 32]];
1144        let p2sh_keypairs: Vec<_> = p2sh_seeds.iter().map(|s| create_keypair(*s)).collect();
1145        let p2sh_pubkeys: Vec<_> = p2sh_keypairs.iter().map(|(_, pk)| *pk).collect();
1146        let redeem_script = create_multisig_script(2, &p2sh_pubkeys);
1147        let script_buf = ScriptBuf::from_bytes(redeem_script.clone());
1148        let script_hash = script_buf.script_hash();
1149        let p2sh_address = PlatformAddress::P2sh(*script_hash.as_byte_array());
1150
1151        // === Signable bytes (same for both in this test) ===
1152        let signable_bytes = b"combined transaction data to redeem both outputs";
1153
1154        // === Redeem P2PKH ===
1155        let p2pkh_sig = sign_data(signable_bytes, &p2pkh_secret);
1156        let p2pkh_witness = AddressWitness::P2pkh {
1157            signature: BinaryData::new(p2pkh_sig),
1158        };
1159        let p2pkh_result =
1160            p2pkh_address.verify_bytes_against_witness(&p2pkh_witness, signable_bytes);
1161        assert!(
1162            p2pkh_result.is_ok(),
1163            "P2PKH redemption should succeed: {:?}",
1164            p2pkh_result
1165        );
1166
1167        // === Redeem P2SH (using keys 0 and 2) ===
1168        let p2sh_sig0 = sign_data(signable_bytes, &p2sh_keypairs[0].0);
1169        let p2sh_sig2 = sign_data(signable_bytes, &p2sh_keypairs[2].0);
1170        let p2sh_witness = AddressWitness::P2sh {
1171            signatures: vec![BinaryData::new(p2sh_sig0), BinaryData::new(p2sh_sig2)],
1172            redeem_script: BinaryData::new(redeem_script),
1173        };
1174        let p2sh_result = p2sh_address.verify_bytes_against_witness(&p2sh_witness, signable_bytes);
1175        assert!(
1176            p2sh_result.is_ok(),
1177            "P2SH redemption should succeed: {:?}",
1178            p2sh_result
1179        );
1180
1181        // Both outputs successfully redeemed!
1182    }
1183
1184    #[test]
1185    fn test_witness_type_mismatch() {
1186        // Create P2PKH address
1187        let seed = [1u8; 32];
1188        let (_, public_key) = create_keypair(seed);
1189        let pubkey_hash = public_key.pubkey_hash();
1190        let p2pkh_address = PlatformAddress::P2pkh(*pubkey_hash.as_byte_array());
1191
1192        // Create P2SH address
1193        let p2sh_hash = [0xABu8; 20];
1194        let p2sh_address = PlatformAddress::P2sh(p2sh_hash);
1195
1196        let signable_bytes = b"test data";
1197
1198        // Try P2SH witness on P2PKH address
1199        let p2sh_witness = AddressWitness::P2sh {
1200            signatures: vec![BinaryData::new(vec![0x30, 0x44])],
1201            redeem_script: BinaryData::new(vec![0x52]),
1202        };
1203        let result = p2pkh_address.verify_bytes_against_witness(&p2sh_witness, signable_bytes);
1204        assert!(result.is_err());
1205        assert!(result
1206            .unwrap_err()
1207            .to_string()
1208            .contains("P2PKH address requires P2pkh witness"));
1209
1210        // Try P2PKH witness on P2SH address
1211        let p2pkh_witness = AddressWitness::P2pkh {
1212            signature: BinaryData::new(vec![0x30, 0x44]),
1213        };
1214        let result = p2sh_address.verify_bytes_against_witness(&p2pkh_witness, signable_bytes);
1215        assert!(result.is_err());
1216        assert!(result
1217            .unwrap_err()
1218            .to_string()
1219            .contains("P2SH address requires P2sh witness"));
1220    }
1221
1222    // ========================
1223    // Bech32m encoding tests (DIP-0018)
1224    // ========================
1225
1226    #[test]
1227    fn test_bech32m_p2pkh_mainnet_roundtrip() {
1228        // Test P2PKH address roundtrip on mainnet
1229        let hash: [u8; 20] = [
1230            0xf7, 0xda, 0x0a, 0x2b, 0x5c, 0xbd, 0x4f, 0xf6, 0xbb, 0x2c, 0x4d, 0x89, 0xb6, 0x7d,
1231            0x2f, 0x3f, 0xfe, 0xec, 0x05, 0x25,
1232        ];
1233        let address = PlatformAddress::P2pkh(hash);
1234
1235        // Encode to bech32m
1236        let encoded = address.to_bech32m_string(Network::Mainnet);
1237
1238        // Verify exact encoding
1239        assert_eq!(
1240            encoded, "dash1krma5z3ttj75la4m93xcndna9ullamq9y5e9n5rs",
1241            "P2PKH mainnet encoding mismatch"
1242        );
1243
1244        // Decode and verify roundtrip
1245        let decoded =
1246            PlatformAddress::from_bech32m_string(&encoded).expect("decoding should succeed");
1247        assert_eq!(decoded, address);
1248    }
1249
1250    #[test]
1251    fn test_bech32m_p2pkh_testnet_roundtrip() {
1252        // Test P2PKH address roundtrip on testnet
1253        let hash: [u8; 20] = [
1254            0xf7, 0xda, 0x0a, 0x2b, 0x5c, 0xbd, 0x4f, 0xf6, 0xbb, 0x2c, 0x4d, 0x89, 0xb6, 0x7d,
1255            0x2f, 0x3f, 0xfe, 0xec, 0x05, 0x25,
1256        ];
1257        let address = PlatformAddress::P2pkh(hash);
1258
1259        // Encode to bech32m
1260        let encoded = address.to_bech32m_string(Network::Testnet);
1261
1262        // Verify exact encoding
1263        assert_eq!(
1264            encoded, "tdash1krma5z3ttj75la4m93xcndna9ullamq9y5fzq2j7",
1265            "P2PKH testnet encoding mismatch"
1266        );
1267
1268        // Decode and verify roundtrip
1269        let decoded =
1270            PlatformAddress::from_bech32m_string(&encoded).expect("decoding should succeed");
1271        assert_eq!(decoded, address);
1272    }
1273
1274    #[test]
1275    fn test_bech32m_p2sh_mainnet_roundtrip() {
1276        // Test P2SH address roundtrip on mainnet
1277        let hash: [u8; 20] = [
1278            0x43, 0xfa, 0x18, 0x3c, 0xf3, 0xfb, 0x6e, 0x9e, 0x7d, 0xc6, 0x2b, 0x69, 0x2a, 0xeb,
1279            0x4f, 0xc8, 0xd8, 0x04, 0x56, 0x36,
1280        ];
1281        let address = PlatformAddress::P2sh(hash);
1282
1283        // Encode to bech32m
1284        let encoded = address.to_bech32m_string(Network::Mainnet);
1285
1286        // Verify exact encoding
1287        assert_eq!(
1288            encoded, "dash1sppl5xpu70aka8nacc4kj2htflydspzkxch4cad6",
1289            "P2SH mainnet encoding mismatch"
1290        );
1291
1292        // Decode and verify roundtrip
1293        let decoded =
1294            PlatformAddress::from_bech32m_string(&encoded).expect("decoding should succeed");
1295        assert_eq!(decoded, address);
1296    }
1297
1298    #[test]
1299    fn test_bech32m_p2sh_testnet_roundtrip() {
1300        // Test P2SH address roundtrip on testnet
1301        let hash: [u8; 20] = [
1302            0x43, 0xfa, 0x18, 0x3c, 0xf3, 0xfb, 0x6e, 0x9e, 0x7d, 0xc6, 0x2b, 0x69, 0x2a, 0xeb,
1303            0x4f, 0xc8, 0xd8, 0x04, 0x56, 0x36,
1304        ];
1305        let address = PlatformAddress::P2sh(hash);
1306
1307        // Encode to bech32m
1308        let encoded = address.to_bech32m_string(Network::Testnet);
1309
1310        // Verify exact encoding
1311        assert_eq!(
1312            encoded, "tdash1sppl5xpu70aka8nacc4kj2htflydspzkxc8jtru5",
1313            "P2SH testnet encoding mismatch"
1314        );
1315
1316        // Decode and verify roundtrip
1317        let decoded =
1318            PlatformAddress::from_bech32m_string(&encoded).expect("decoding should succeed");
1319        assert_eq!(decoded, address);
1320    }
1321
1322    #[test]
1323    fn test_bech32m_devnet_uses_testnet_hrp() {
1324        let hash: [u8; 20] = [0xAB; 20];
1325        let address = PlatformAddress::P2pkh(hash);
1326
1327        // Devnet should use testnet HRP
1328        let encoded = address.to_bech32m_string(Network::Devnet);
1329        assert!(
1330            encoded.starts_with("tdash1"),
1331            "Devnet address should start with 'tdash1', got: {}",
1332            encoded
1333        );
1334    }
1335
1336    #[test]
1337    fn test_bech32m_regtest_uses_testnet_hrp() {
1338        let hash: [u8; 20] = [0xAB; 20];
1339        let address = PlatformAddress::P2pkh(hash);
1340
1341        // Regtest should use testnet HRP
1342        let encoded = address.to_bech32m_string(Network::Regtest);
1343        assert!(
1344            encoded.starts_with("tdash1"),
1345            "Regtest address should start with 'tdash1', got: {}",
1346            encoded
1347        );
1348    }
1349
1350    #[test]
1351    fn test_bech32m_invalid_hrp_fails() {
1352        let wrong_hrp = Hrp::parse("bitcoin").unwrap();
1353        let payload: [u8; 21] = [0x00; 21];
1354        let wrong_hrp_address = bech32::encode::<Bech32m>(wrong_hrp, &payload).unwrap();
1355
1356        let result = PlatformAddress::from_bech32m_string(&wrong_hrp_address);
1357        assert!(result.is_err());
1358        let err = result.unwrap_err();
1359        assert!(
1360            err.to_string().contains("not a platform address"),
1361            "Error should mention non-platform HRP: {}",
1362            err
1363        );
1364    }
1365
1366    #[test]
1367    fn test_bech32m_invalid_checksum_fails() {
1368        // Create a valid address, then corrupt the checksum
1369        let hash: [u8; 20] = [0xAB; 20];
1370        let address = PlatformAddress::P2pkh(hash);
1371        let mut encoded = address.to_bech32m_string(Network::Mainnet);
1372
1373        // Corrupt the last character (part of checksum)
1374        let last_char = encoded.pop().unwrap();
1375        let corrupted_char = if last_char == 'q' { 'p' } else { 'q' };
1376        encoded.push(corrupted_char);
1377
1378        let result = PlatformAddress::from_bech32m_string(&encoded);
1379        assert!(result.is_err(), "Should fail with corrupted checksum");
1380    }
1381
1382    #[test]
1383    fn test_bech32m_invalid_type_byte_fails() {
1384        // Manually construct an address with invalid type byte (0x02)
1385        // We need to use the bech32 crate directly for this
1386        let hrp = Hrp::parse("dash").unwrap();
1387        let invalid_payload: [u8; 21] = [0x02; 21]; // type byte 0x02 is invalid
1388        let encoded = bech32::encode::<Bech32m>(hrp, &invalid_payload).unwrap();
1389
1390        let result = PlatformAddress::from_bech32m_string(&encoded);
1391        assert!(result.is_err());
1392        let err = result.unwrap_err();
1393        assert!(
1394            err.to_string().contains("invalid address type"),
1395            "Error should mention invalid type: {}",
1396            err
1397        );
1398    }
1399
1400    #[test]
1401    fn test_bech32m_too_short_fails() {
1402        // Construct an address with too few bytes
1403        let hrp = Hrp::parse("dash").unwrap();
1404        let short_payload: [u8; 10] = [0xb0; 10]; // Only 10 bytes instead of 21
1405        let encoded = bech32::encode::<Bech32m>(hrp, &short_payload).unwrap();
1406
1407        let result = PlatformAddress::from_bech32m_string(&encoded);
1408        assert!(result.is_err());
1409        let err = result.unwrap_err();
1410        assert!(
1411            err.to_string().contains("invalid Platform address length"),
1412            "Error should mention invalid length: {}",
1413            err
1414        );
1415    }
1416
1417    #[test]
1418    fn test_bech32m_from_str_trait() {
1419        // Test the FromStr trait implementation
1420        let hash: [u8; 20] = [
1421            0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66,
1422            0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc,
1423        ];
1424        let original = PlatformAddress::P2pkh(hash);
1425
1426        // Encode and then parse via FromStr
1427        let encoded = original.to_bech32m_string(Network::Testnet);
1428        let parsed: PlatformAddress = encoded.parse().expect("parsing should succeed");
1429
1430        assert_eq!(parsed, original);
1431    }
1432
1433    #[test]
1434    fn test_bech32m_case_insensitive() {
1435        // Per DIP-0018, addresses must be lowercase or uppercase (not mixed)
1436        // The bech32 crate should handle this
1437        let hash: [u8; 20] = [0xAB; 20];
1438        let address = PlatformAddress::P2pkh(hash);
1439
1440        let lowercase = address.to_bech32m_string(Network::Mainnet);
1441        let uppercase = lowercase.to_uppercase();
1442
1443        // Both should decode to the same address
1444        let decoded_lower = PlatformAddress::from_bech32m_string(&lowercase).unwrap();
1445        let decoded_upper = PlatformAddress::from_bech32m_string(&uppercase).unwrap();
1446
1447        assert_eq!(decoded_lower, decoded_upper);
1448        assert_eq!(decoded_lower, address);
1449    }
1450
1451    #[test]
1452    fn test_bech32m_all_zeros_p2pkh() {
1453        // Edge case: all-zero hash
1454        let address = PlatformAddress::P2pkh([0u8; 20]);
1455        let encoded = address.to_bech32m_string(Network::Mainnet);
1456        let decoded = PlatformAddress::from_bech32m_string(&encoded).unwrap();
1457        assert_eq!(decoded, address);
1458    }
1459
1460    #[test]
1461    fn test_bech32m_all_ones_p2sh() {
1462        // Edge case: all-ones hash
1463        let address = PlatformAddress::P2sh([0xFF; 20]);
1464        let encoded = address.to_bech32m_string(Network::Mainnet);
1465        let decoded = PlatformAddress::from_bech32m_string(&encoded).unwrap();
1466        assert_eq!(decoded, address);
1467    }
1468
1469    #[test]
1470    fn test_hrp_for_network() {
1471        assert_eq!(PlatformAddress::hrp_for_network(Network::Mainnet), "dash");
1472        assert_eq!(PlatformAddress::hrp_for_network(Network::Testnet), "tdash");
1473        assert_eq!(PlatformAddress::hrp_for_network(Network::Devnet), "tdash");
1474        assert_eq!(PlatformAddress::hrp_for_network(Network::Regtest), "tdash");
1475    }
1476
1477    #[test]
1478    fn test_storage_bytes_format() {
1479        // Verify that to_bytes() (using bincode) produces expected format:
1480        // [variant_index (1 byte)] + [hash (20 bytes)]
1481        // P2pkh = variant 0, P2sh = variant 1
1482        let p2pkh = PlatformAddress::P2pkh([0xAB; 20]);
1483        let p2sh = PlatformAddress::P2sh([0xCD; 20]);
1484
1485        let p2pkh_bytes = p2pkh.to_bytes();
1486        let p2sh_bytes = p2sh.to_bytes();
1487
1488        // Verify format: 21 bytes total, first byte is variant index
1489        assert_eq!(p2pkh_bytes.len(), 21);
1490        assert_eq!(p2sh_bytes.len(), 21);
1491        assert_eq!(p2pkh_bytes[0], 0x00, "P2pkh variant index must be 0x00");
1492        assert_eq!(p2sh_bytes[0], 0x01, "P2sh variant index must be 0x01");
1493
1494        // Verify roundtrip through from_bytes
1495        let p2pkh_decoded = PlatformAddress::from_bytes(&p2pkh_bytes).unwrap();
1496        let p2sh_decoded = PlatformAddress::from_bytes(&p2sh_bytes).unwrap();
1497        assert_eq!(p2pkh_decoded, p2pkh);
1498        assert_eq!(p2sh_decoded, p2sh);
1499    }
1500
1501    #[test]
1502    fn test_bech32m_uses_different_type_bytes_than_storage() {
1503        // Verify that bech32m encoding uses type bytes (0xb0/0x80)
1504        // while storage (bincode) uses variant indices (0x00/0x01)
1505        let p2pkh = PlatformAddress::P2pkh([0xAB; 20]);
1506        let p2sh = PlatformAddress::P2sh([0xCD; 20]);
1507
1508        // Storage bytes (bincode) use variant indices 0x00/0x01
1509        assert_eq!(p2pkh.to_bytes()[0], 0x00);
1510        assert_eq!(p2sh.to_bytes()[0], 0x01);
1511
1512        // Bech32m encoding uses 0xb0/0xb8 (verified by successful roundtrip)
1513        let p2pkh_encoded = p2pkh.to_bech32m_string(Network::Mainnet);
1514        let p2sh_encoded = p2sh.to_bech32m_string(Network::Mainnet);
1515
1516        let p2pkh_decoded = PlatformAddress::from_bech32m_string(&p2pkh_encoded).unwrap();
1517        let p2sh_decoded = PlatformAddress::from_bech32m_string(&p2sh_encoded).unwrap();
1518
1519        assert_eq!(p2pkh_decoded, p2pkh);
1520        assert_eq!(p2sh_decoded, p2sh);
1521    }
1522
1523    #[test]
1524    fn test_is_mainnet_bech32m_mainnet_is_true() {
1525        let encoded = PlatformAddress::P2pkh([0x11; 20]).to_bech32m_string(Network::Mainnet);
1526        assert!(encoded.starts_with("dash1"));
1527        assert!(PlatformAddress::is_mainnet_bech32m(&encoded).unwrap());
1528    }
1529
1530    #[test]
1531    fn test_is_mainnet_bech32m_all_non_mainnet_networks_are_false() {
1532        // Testnet, Devnet, and Regtest all share the `tdash` HRP, so all three
1533        // classify as non-mainnet (false) — the only truthful answer DIP-0018
1534        // allows from the address string alone.
1535        for network in [Network::Testnet, Network::Devnet, Network::Regtest] {
1536            let encoded = PlatformAddress::P2pkh([0x22; 20]).to_bech32m_string(network);
1537            assert!(encoded.starts_with("tdash1"), "network {network:?}");
1538            assert!(
1539                !PlatformAddress::is_mainnet_bech32m(&encoded).unwrap(),
1540                "network {network:?} must classify as non-mainnet"
1541            );
1542        }
1543    }
1544
1545    #[test]
1546    fn test_is_mainnet_bech32m_is_case_insensitive() {
1547        let mainnet = PlatformAddress::P2pkh([0x33; 20])
1548            .to_bech32m_string(Network::Mainnet)
1549            .to_uppercase();
1550        assert!(mainnet.starts_with("DASH1"));
1551        assert!(PlatformAddress::is_mainnet_bech32m(&mainnet).unwrap());
1552
1553        let testnet = PlatformAddress::P2pkh([0x44; 20])
1554            .to_bech32m_string(Network::Testnet)
1555            .to_uppercase();
1556        assert!(testnet.starts_with("TDASH1"));
1557        assert!(!PlatformAddress::is_mainnet_bech32m(&testnet).unwrap());
1558    }
1559
1560    #[test]
1561    fn test_is_mainnet_bech32m_non_platform_hrp_errors() {
1562        // Valid Bitcoin bech32 address: decode succeeds, HRP "bc" triggers error.
1563        let err = PlatformAddress::is_mainnet_bech32m("bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4")
1564            .unwrap_err();
1565        assert!(
1566            err.to_string().contains("not a platform address"),
1567            "unexpected error: {err}"
1568        );
1569    }
1570
1571    #[test]
1572    fn test_is_mainnet_bech32m_malformed_data_part_errors() {
1573        // `dash1!` has a valid HRP but `!` is not a bech32 character.
1574        // Previously this returned Ok(true) (the HRP-only check); now it must
1575        // return an error because bech32::decode validates the full string.
1576        assert!(
1577            PlatformAddress::is_mainnet_bech32m("dash1!").is_err(),
1578            "dash1! must error, not return Ok(true)"
1579        );
1580    }
1581
1582    #[test]
1583    fn test_is_mainnet_bech32m_missing_separator_errors() {
1584        let err = PlatformAddress::is_mainnet_bech32m("nodelimiterhere").unwrap_err();
1585        // bech32::decode returns "parsing failed" for strings without separator
1586        assert!(
1587            err.to_string().contains("parsing failed") || err.to_string().contains("separator"),
1588            "unexpected error: {err}"
1589        );
1590    }
1591
1592    #[test]
1593    fn test_is_mainnet_bech32m_empty_errors() {
1594        let err = PlatformAddress::is_mainnet_bech32m("").unwrap_err();
1595        assert!(
1596            err.to_string().contains("parsing failed") || err.to_string().contains("separator"),
1597            "unexpected error: {err}"
1598        );
1599    }
1600}