1#[cfg(feature = "derive")]
27use crate::encode::Descriptor;
28#[cfg(feature = "derive")]
29use crate::error::Error;
30#[cfg(feature = "derive")]
31use bitcoin::NetworkKind;
32#[cfg(feature = "derive")]
33use bitcoin::address::NetworkUnchecked;
34#[cfg(feature = "derive")]
35use bitcoin::bip32::{ChainCode, ChildNumber, Fingerprint, Xpub};
36#[cfg(feature = "derive")]
37use bitcoin::secp256k1::PublicKey;
38#[cfg(feature = "derive")]
39use bitcoin::{Address, Network};
40
41#[cfg(feature = "derive")]
49pub(crate) fn xpub_from_tlv_bytes(idx: u8, bytes: &[u8; 65]) -> Result<Xpub, Error> {
50 let chain_code_bytes: [u8; 32] = bytes[0..32]
51 .try_into()
52 .expect("32-byte slice is statically sized");
53 let chain_code = ChainCode::from(chain_code_bytes);
54 let public_key =
55 PublicKey::from_slice(&bytes[32..65]).map_err(|_| Error::InvalidXpubBytes { idx })?;
56 Ok(Xpub {
57 network: NetworkKind::Main,
58 depth: 0,
59 parent_fingerprint: Fingerprint::default(),
60 child_number: ChildNumber::Normal { index: 0 },
61 public_key,
62 chain_code,
63 })
64}
65
66#[cfg(feature = "derive")]
67impl Descriptor {
68 pub fn derive_address(
93 &self,
94 chain: u32,
95 index: u32,
96 network: Network,
97 ) -> Result<Address<NetworkUnchecked>, Error> {
98 if self.use_site_path.wildcard_hardened {
100 return Err(Error::HardenedPublicDerivation);
101 }
102 if let Some(alts) = &self.use_site_path.multipath {
104 if (chain as usize) >= alts.len() {
105 return Err(Error::ChainIndexOutOfRange {
106 chain,
107 alt_count: alts.len(),
108 });
109 }
110 if alts[chain as usize].hardened {
111 return Err(Error::HardenedPublicDerivation);
112 }
113 } else if chain != 0 {
114 return Err(Error::ChainIndexOutOfRange {
115 chain,
116 alt_count: 0,
117 });
118 }
119
120 let desc = crate::to_miniscript::to_miniscript_descriptor(self, chain)?;
121 let definite =
122 desc.at_derivation_index(index)
123 .map_err(|e| Error::AddressDerivationFailed {
124 detail: e.to_string(),
125 })?;
126 let addr = definite
127 .address(network)
128 .map_err(|e| Error::AddressDerivationFailed {
129 detail: e.to_string(),
130 })?;
131 Ok(addr.into_unchecked())
132 }
133}
134
135#[cfg(all(test, feature = "derive"))]
136mod tests {
137 use super::*;
138 use crate::origin_path::{OriginPath, PathComponent, PathDecl, PathDeclPaths};
139 use crate::tag::Tag;
140 use crate::tlv::TlvSection;
141 use crate::tree::{Body, Node};
142 use crate::use_site_path::{Alternative, UseSitePath};
143
144 #[test]
147 fn xpub_from_tlv_bytes_rejects_invalid_pubkey() {
148 let bytes = [0u8; 65];
150 assert!(matches!(
151 xpub_from_tlv_bytes(7, &bytes),
152 Err(Error::InvalidXpubBytes { idx: 7 })
153 ));
154 }
155
156 fn bip84_origin() -> OriginPath {
157 OriginPath {
158 components: vec![
159 PathComponent {
160 hardened: true,
161 value: 84,
162 },
163 PathComponent {
164 hardened: true,
165 value: 0,
166 },
167 PathComponent {
168 hardened: true,
169 value: 0,
170 },
171 ],
172 }
173 }
174
175 fn one_test_xpub_bytes() -> [u8; 65] {
176 let mut bytes = [0u8; 65];
177 bytes[0..32].copy_from_slice(&[0x42; 32]);
178 bytes[32] = 0x02;
179 bytes[33..].copy_from_slice(&[
180 0x79, 0xBE, 0x66, 0x7E, 0xF9, 0xDC, 0xBB, 0xAC, 0x55, 0xA0, 0x62, 0x95, 0xCE, 0x87,
181 0x0B, 0x07, 0x02, 0x9B, 0xFC, 0xDB, 0x2D, 0xCE, 0x28, 0xD9, 0x59, 0xF2, 0x81, 0x5B,
182 0x16, 0xF8, 0x17, 0x98,
183 ]);
184 bytes
185 }
186
187 #[test]
188 fn derive_address_missing_pubkey_for_partial_keys() {
189 let d = Descriptor {
191 n: 2,
192 path_decl: PathDecl {
193 n: 2,
194 paths: PathDeclPaths::Shared(OriginPath {
195 components: vec![
196 PathComponent {
197 hardened: true,
198 value: 48,
199 },
200 PathComponent {
201 hardened: true,
202 value: 0,
203 },
204 PathComponent {
205 hardened: true,
206 value: 0,
207 },
208 PathComponent {
209 hardened: true,
210 value: 2,
211 },
212 ],
213 }),
214 },
215 use_site_path: UseSitePath::standard_multipath(),
216 tree: Node {
217 tag: Tag::Wsh,
218 body: Body::Children(vec![Node {
219 tag: Tag::SortedMulti,
220 body: Body::MultiKeys {
221 k: 2,
222 indices: vec![0, 1],
223 },
224 }]),
225 },
226 tlv: {
227 let mut t = TlvSection::new_empty();
228 t.pubkeys = Some(vec![(0u8, one_test_xpub_bytes())]);
229 t
230 },
231 };
232 let err = d.derive_address(0, 0, Network::Bitcoin).unwrap_err();
233 assert!(matches!(err, Error::MissingPubkey { idx: 1 }));
234 }
235
236 #[test]
237 fn derive_address_chain_out_of_range() {
238 let d = Descriptor {
239 n: 1,
240 path_decl: PathDecl {
241 n: 1,
242 paths: PathDeclPaths::Shared(bip84_origin()),
243 },
244 use_site_path: UseSitePath::standard_multipath(), tree: Node {
246 tag: Tag::Wpkh,
247 body: Body::KeyArg { index: 0 },
248 },
249 tlv: {
250 let mut t = TlvSection::new_empty();
251 t.pubkeys = Some(vec![(0u8, one_test_xpub_bytes())]);
252 t
253 },
254 };
255 let err = d.derive_address(5, 0, Network::Bitcoin).unwrap_err();
256 assert!(matches!(
257 err,
258 Error::ChainIndexOutOfRange {
259 chain: 5,
260 alt_count: 2
261 }
262 ));
263 }
264
265 #[test]
266 fn derive_address_hardened_wildcard_rejected() {
267 let d = Descriptor {
268 n: 1,
269 path_decl: PathDecl {
270 n: 1,
271 paths: PathDeclPaths::Shared(bip84_origin()),
272 },
273 use_site_path: UseSitePath {
274 multipath: Some(vec![
275 Alternative {
276 hardened: false,
277 value: 0,
278 },
279 Alternative {
280 hardened: false,
281 value: 1,
282 },
283 ]),
284 wildcard_hardened: true,
285 },
286 tree: Node {
287 tag: Tag::Wpkh,
288 body: Body::KeyArg { index: 0 },
289 },
290 tlv: {
291 let mut t = TlvSection::new_empty();
292 t.pubkeys = Some(vec![(0u8, one_test_xpub_bytes())]);
293 t
294 },
295 };
296 let err = d.derive_address(0, 0, Network::Bitcoin).unwrap_err();
297 assert!(matches!(err, Error::HardenedPublicDerivation));
298 }
299}