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