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