dpp/address_funds/
orchard_address.rs1use bech32::{Bech32m, Hrp};
2use dashcore::Network;
3
4use crate::address_funds::platform_address::classify_platform_hrp;
5use crate::address_funds::PlatformAddress;
6use crate::ProtocolError;
7
8pub const ORCHARD_DIVERSIFIER_SIZE: usize = 11;
10pub const ORCHARD_PKD_SIZE: usize = 32;
12pub const ORCHARD_ADDRESS_SIZE: usize = ORCHARD_DIVERSIFIER_SIZE + ORCHARD_PKD_SIZE;
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
33pub struct OrchardAddress(grovedb_commitment_tree::PaymentAddress);
34
35impl OrchardAddress {
36 pub const ORCHARD_TYPE: u8 = 0x10;
39
40 pub fn inner(&self) -> &grovedb_commitment_tree::PaymentAddress {
42 &self.0
43 }
44
45 pub fn into_inner(self) -> grovedb_commitment_tree::PaymentAddress {
47 self.0
48 }
49
50 pub fn from_raw_bytes(bytes: &[u8; ORCHARD_ADDRESS_SIZE]) -> Result<Self, ProtocolError> {
55 let addr =
56 Option::from(grovedb_commitment_tree::PaymentAddress::from_raw_address_bytes(bytes))
57 .ok_or_else(|| {
58 ProtocolError::DecodingError(
59 "OrchardAddress pk_d is not a valid Pallas curve point".to_string(),
60 )
61 })?;
62 Ok(Self(addr))
63 }
64
65 pub fn to_raw_bytes(&self) -> [u8; ORCHARD_ADDRESS_SIZE] {
67 self.0.to_raw_address_bytes()
68 }
69
70 pub fn to_bech32m_string(&self, network: Network) -> String {
77 let hrp_str = PlatformAddress::hrp_for_network(network);
78 let hrp = Hrp::parse(hrp_str).expect("HRP is valid");
79
80 let raw = self.to_raw_bytes();
81 let mut payload = Vec::with_capacity(1 + ORCHARD_ADDRESS_SIZE);
82 payload.push(Self::ORCHARD_TYPE);
83 payload.extend_from_slice(&raw);
84
85 bech32::encode::<Bech32m>(hrp, &payload).expect("encoding should succeed")
86 }
87
88 pub fn from_bech32m_string(s: &str) -> Result<Self, ProtocolError> {
99 let (hrp, data) =
100 bech32::decode(s).map_err(|e| ProtocolError::DecodingError(format!("{}", e)))?;
101
102 classify_platform_hrp(&hrp.as_str().to_ascii_lowercase())?;
103
104 if data.len() != 1 + ORCHARD_ADDRESS_SIZE {
106 return Err(ProtocolError::DecodingError(format!(
107 "invalid Orchard address length: expected {} bytes, got {}",
108 1 + ORCHARD_ADDRESS_SIZE,
109 data.len()
110 )));
111 }
112
113 if data[0] != Self::ORCHARD_TYPE {
114 return Err(ProtocolError::DecodingError(format!(
115 "invalid Orchard address type byte: expected 0x{:02x}, got 0x{:02x}",
116 Self::ORCHARD_TYPE,
117 data[0]
118 )));
119 }
120
121 let mut raw = [0u8; ORCHARD_ADDRESS_SIZE];
122 raw.copy_from_slice(&data[1..]);
123 Self::from_raw_bytes(&raw)
124 }
125}
126
127impl From<grovedb_commitment_tree::PaymentAddress> for OrchardAddress {
129 fn from(addr: grovedb_commitment_tree::PaymentAddress) -> Self {
130 Self(addr)
131 }
132}
133
134impl From<&grovedb_commitment_tree::PaymentAddress> for OrchardAddress {
136 fn from(addr: &grovedb_commitment_tree::PaymentAddress) -> Self {
137 Self(*addr)
138 }
139}
140
141impl std::fmt::Display for OrchardAddress {
142 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
143 let raw = self.to_raw_bytes();
144 write!(
145 f,
146 "Orchard(d={}, pk_d={})",
147 hex::encode(&raw[..ORCHARD_DIVERSIFIER_SIZE]),
148 hex::encode(&raw[ORCHARD_DIVERSIFIER_SIZE..])
149 )
150 }
151}
152
153#[cfg(test)]
154mod tests {
155 use super::*;
156 use bech32::Hrp;
157
158 fn test_orchard_address() -> OrchardAddress {
159 use grovedb_commitment_tree::{FullViewingKey, Scope, SpendingKey};
160 let sk = SpendingKey::from_bytes([42u8; 32]).unwrap();
161 let fvk = FullViewingKey::from(&sk);
162 let payment_address = fvk.address_at(0u32, Scope::External);
163 OrchardAddress::from(payment_address)
164 }
165
166 #[test]
167 fn test_orchard_address_raw_bytes_roundtrip() {
168 let address = test_orchard_address();
169 let raw = address.to_raw_bytes();
170 assert_eq!(raw.len(), 43);
171
172 let recovered = OrchardAddress::from_raw_bytes(&raw).unwrap();
173 assert_eq!(recovered, address);
174 }
175
176 #[test]
177 fn test_orchard_bech32m_mainnet_roundtrip() {
178 let address = test_orchard_address();
179
180 let encoded = address.to_bech32m_string(Network::Mainnet);
181 assert!(
182 encoded.starts_with("dash1z"),
183 "Orchard mainnet address should start with 'dash1z', got: {}",
184 encoded
185 );
186
187 let decoded =
188 OrchardAddress::from_bech32m_string(&encoded).expect("decoding should succeed");
189 assert_eq!(decoded, address);
190 }
191
192 #[test]
193 fn test_orchard_bech32m_testnet_roundtrip() {
194 let address = test_orchard_address();
195
196 let encoded = address.to_bech32m_string(Network::Testnet);
197 assert!(
198 encoded.starts_with("tdash1z"),
199 "Orchard testnet address should start with 'tdash1z', got: {}",
200 encoded
201 );
202
203 let decoded =
204 OrchardAddress::from_bech32m_string(&encoded).expect("decoding should succeed");
205 assert_eq!(decoded, address);
206 }
207
208 #[test]
209 fn test_orchard_bech32m_wrong_type_byte_fails() {
210 let hrp = Hrp::parse("dash").unwrap();
212 let mut payload = vec![PlatformAddress::P2PKH_TYPE]; payload.extend_from_slice(&[0u8; 43]);
214 let encoded = bech32::encode::<Bech32m>(hrp, &payload).unwrap();
215
216 let result = OrchardAddress::from_bech32m_string(&encoded);
217 assert!(result.is_err());
218 assert!(result
219 .unwrap_err()
220 .to_string()
221 .contains("invalid Orchard address type byte"));
222 }
223
224 #[test]
225 fn test_orchard_bech32m_wrong_length_fails() {
226 let hrp = Hrp::parse("dash").unwrap();
228 let mut payload = vec![OrchardAddress::ORCHARD_TYPE];
229 payload.extend_from_slice(&[0u8; 20]);
230 let encoded = bech32::encode::<Bech32m>(hrp, &payload).unwrap();
231
232 let result = OrchardAddress::from_bech32m_string(&encoded);
233 assert!(result.is_err());
234 assert!(result
235 .unwrap_err()
236 .to_string()
237 .contains("invalid Orchard address length"));
238 }
239
240 #[test]
241 fn test_orchard_and_platform_addresses_are_distinguishable() {
242 let p2pkh = PlatformAddress::P2pkh([0xAB; 20]);
243 let p2sh = PlatformAddress::P2sh([0xAB; 20]);
244 let orchard = test_orchard_address();
245
246 let p2pkh_enc = p2pkh.to_bech32m_string(Network::Mainnet);
247 let p2sh_enc = p2sh.to_bech32m_string(Network::Mainnet);
248 let orchard_enc = orchard.to_bech32m_string(Network::Mainnet);
249
250 assert!(p2pkh_enc.starts_with("dash1k"), "P2PKH: {}", p2pkh_enc);
252 assert!(p2sh_enc.starts_with("dash1s"), "P2SH: {}", p2sh_enc);
253 assert!(
254 orchard_enc.starts_with("dash1z"),
255 "Orchard: {}",
256 orchard_enc
257 );
258
259 assert!(PlatformAddress::from_bech32m_string(&orchard_enc).is_err());
261 assert!(OrchardAddress::from_bech32m_string(&p2pkh_enc).is_err());
262 }
263
264 #[test]
265 fn test_orchard_address_from_raw_bytes_invalid_pk_d() {
266 let mut raw = [0u8; 43];
268 raw[0] = 0x01; assert!(OrchardAddress::from_raw_bytes(&raw).is_err());
270 }
271
272 #[test]
273 fn test_orchard_address_display() {
274 let address = test_orchard_address();
275 let display = format!("{}", address);
276 assert!(display.starts_with("Orchard(d="));
277 assert!(display.contains("pk_d="));
278 }
279}