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