Skip to main content

dpp/address_funds/
platform_address.rs

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