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