Skip to main content

dpp/address_funds/
platform_address.rs

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