Skip to content

Commit b5eb317

Browse files
committed
desc keys: unit tests for multipath key expressions
1 parent 34472bb commit b5eb317

File tree

1 file changed

+186
-1
lines changed

1 file changed

+186
-1
lines changed

src/descriptor/key.rs

Lines changed: 186 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1050,8 +1050,12 @@ mod test {
10501050
use core::str::FromStr;
10511051

10521052
use bitcoin::secp256k1;
1053+
use bitcoin::util::bip32;
10531054

1054-
use super::{DescriptorKeyParseError, DescriptorPublicKey, DescriptorSecretKey};
1055+
use super::{
1056+
DescriptorKeyParseError, DescriptorMultiXKey, DescriptorPublicKey, DescriptorSecretKey,
1057+
Wildcard,
1058+
};
10551059
use crate::prelude::*;
10561060

10571061
#[test]
@@ -1236,4 +1240,185 @@ mod test {
12361240
b"\xb0\x59\x11\x6a"
12371241
);
12381242
}
1243+
1244+
fn get_multipath_xpub(key_str: &str) -> DescriptorMultiXKey<bip32::ExtendedPubKey> {
1245+
let desc_key = DescriptorPublicKey::from_str(key_str).unwrap();
1246+
match desc_key {
1247+
DescriptorPublicKey::MultiXPub(xpub) => xpub,
1248+
_ => unreachable!(),
1249+
}
1250+
}
1251+
1252+
fn get_multipath_xprv(key_str: &str) -> DescriptorMultiXKey<bip32::ExtendedPrivKey> {
1253+
let desc_key = DescriptorSecretKey::from_str(key_str).unwrap();
1254+
match desc_key {
1255+
DescriptorSecretKey::MultiXPrv(xprv) => xprv,
1256+
_ => unreachable!(),
1257+
}
1258+
}
1259+
1260+
#[test]
1261+
fn multipath_extended_keys() {
1262+
let secp = secp256k1::Secp256k1::signing_only();
1263+
1264+
// We can have a key in a descriptor that has multiple paths
1265+
let xpub = get_multipath_xpub("tpubDBrgjcxBxnXyL575sHdkpKohWu5qHKoQ7TJXKNrYznh5fVEGBv89hA8ENW7A8MFVpFUSvgLqc4Nj1WZcpePX6rrxviVtPowvMuGF5rdT2Vi/2/<0;1;42;9854>");
1266+
assert_eq!(
1267+
xpub.derivation_paths.paths(),
1268+
&vec![
1269+
bip32::DerivationPath::from_str("m/2/0").unwrap(),
1270+
bip32::DerivationPath::from_str("m/2/1").unwrap(),
1271+
bip32::DerivationPath::from_str("m/2/42").unwrap(),
1272+
bip32::DerivationPath::from_str("m/2/9854").unwrap()
1273+
],
1274+
);
1275+
assert_eq!(
1276+
xpub,
1277+
get_multipath_xpub(&DescriptorPublicKey::MultiXPub(xpub.clone()).to_string())
1278+
);
1279+
// Even if it's in the middle of the derivation path.
1280+
let xpub = get_multipath_xpub("tpubDBrgjcxBxnXyL575sHdkpKohWu5qHKoQ7TJXKNrYznh5fVEGBv89hA8ENW7A8MFVpFUSvgLqc4Nj1WZcpePX6rrxviVtPowvMuGF5rdT2Vi/2/<0;1;9854>/0/5/10");
1281+
assert_eq!(
1282+
xpub.derivation_paths.paths(),
1283+
&vec![
1284+
bip32::DerivationPath::from_str("m/2/0/0/5/10").unwrap(),
1285+
bip32::DerivationPath::from_str("m/2/1/0/5/10").unwrap(),
1286+
bip32::DerivationPath::from_str("m/2/9854/0/5/10").unwrap()
1287+
],
1288+
);
1289+
assert_eq!(
1290+
xpub,
1291+
get_multipath_xpub(&DescriptorPublicKey::MultiXPub(xpub.clone()).to_string())
1292+
);
1293+
// Even if it is a wildcard extended key.
1294+
let xpub = get_multipath_xpub("tpubDBrgjcxBxnXyL575sHdkpKohWu5qHKoQ7TJXKNrYznh5fVEGBv89hA8ENW7A8MFVpFUSvgLqc4Nj1WZcpePX6rrxviVtPowvMuGF5rdT2Vi/2/<0;1;9854>/3456/9876/*");
1295+
assert_eq!(xpub.wildcard, Wildcard::Unhardened);
1296+
assert_eq!(
1297+
xpub.derivation_paths.paths(),
1298+
&vec![
1299+
bip32::DerivationPath::from_str("m/2/0/3456/9876").unwrap(),
1300+
bip32::DerivationPath::from_str("m/2/1/3456/9876").unwrap(),
1301+
bip32::DerivationPath::from_str("m/2/9854/3456/9876").unwrap()
1302+
],
1303+
);
1304+
assert_eq!(
1305+
xpub,
1306+
get_multipath_xpub(&DescriptorPublicKey::MultiXPub(xpub.clone()).to_string())
1307+
);
1308+
// Also even if it has an origin.
1309+
let xpub = get_multipath_xpub("[abcdef00/0'/1']tpubDBrgjcxBxnXyL575sHdkpKohWu5qHKoQ7TJXKNrYznh5fVEGBv89hA8ENW7A8MFVpFUSvgLqc4Nj1WZcpePX6rrxviVtPowvMuGF5rdT2Vi/<0;1>/*");
1310+
assert_eq!(xpub.wildcard, Wildcard::Unhardened);
1311+
assert_eq!(
1312+
xpub.derivation_paths.paths(),
1313+
&vec![
1314+
bip32::DerivationPath::from_str("m/0").unwrap(),
1315+
bip32::DerivationPath::from_str("m/1").unwrap(),
1316+
],
1317+
);
1318+
assert_eq!(
1319+
xpub,
1320+
get_multipath_xpub(&DescriptorPublicKey::MultiXPub(xpub.clone()).to_string())
1321+
);
1322+
// Also if it has hardened steps in the derivation path. In fact, it can also have hardened
1323+
// indexes even at the step with multiple indexes!
1324+
let xpub = get_multipath_xpub("[abcdef00/0'/1']tpubDBrgjcxBxnXyL575sHdkpKohWu5qHKoQ7TJXKNrYznh5fVEGBv89hA8ENW7A8MFVpFUSvgLqc4Nj1WZcpePX6rrxviVtPowvMuGF5rdT2Vi/9478'/<0';1h>/8h/*'");
1325+
assert_eq!(xpub.wildcard, Wildcard::Hardened);
1326+
assert_eq!(
1327+
xpub.derivation_paths.paths(),
1328+
&vec![
1329+
bip32::DerivationPath::from_str("m/9478'/0'/8'").unwrap(),
1330+
bip32::DerivationPath::from_str("m/9478h/1h/8h").unwrap(),
1331+
],
1332+
);
1333+
assert_eq!(
1334+
xpub,
1335+
get_multipath_xpub(&DescriptorPublicKey::MultiXPub(xpub.clone()).to_string())
1336+
);
1337+
// You can't get the "full derivation path" for a multipath extended public key.
1338+
let desc_key = DescriptorPublicKey::from_str("[abcdef00/0'/1']tpubDBrgjcxBxnXyL575sHdkpKohWu5qHKoQ7TJXKNrYznh5fVEGBv89hA8ENW7A8MFVpFUSvgLqc4Nj1WZcpePX6rrxviVtPowvMuGF5rdT2Vi/9478'/<0';1>/8h/*'").unwrap();
1339+
assert!(desc_key.full_derivation_path().is_none());
1340+
1341+
// All the same but with extended private keys instead of xpubs.
1342+
let xprv = get_multipath_xprv("tprv8ZgxMBicQKsPcwcD4gSnMti126ZiETsuX7qwrtMypr6FBwAP65puFn4v6c3jrN9VwtMRMph6nyT63NrfUL4C3nBzPcduzVSuHD7zbX2JKVc/2/<0;1;42;9854>");
1343+
assert_eq!(
1344+
xprv.derivation_paths.paths(),
1345+
&vec![
1346+
bip32::DerivationPath::from_str("m/2/0").unwrap(),
1347+
bip32::DerivationPath::from_str("m/2/1").unwrap(),
1348+
bip32::DerivationPath::from_str("m/2/42").unwrap(),
1349+
bip32::DerivationPath::from_str("m/2/9854").unwrap()
1350+
],
1351+
);
1352+
assert_eq!(
1353+
xprv,
1354+
get_multipath_xprv(&DescriptorSecretKey::MultiXPrv(xprv.clone()).to_string())
1355+
);
1356+
let xprv = get_multipath_xprv("tprv8ZgxMBicQKsPcwcD4gSnMti126ZiETsuX7qwrtMypr6FBwAP65puFn4v6c3jrN9VwtMRMph6nyT63NrfUL4C3nBzPcduzVSuHD7zbX2JKVc/2/<0;1;9854>/0/5/10");
1357+
assert_eq!(
1358+
xprv.derivation_paths.paths(),
1359+
&vec![
1360+
bip32::DerivationPath::from_str("m/2/0/0/5/10").unwrap(),
1361+
bip32::DerivationPath::from_str("m/2/1/0/5/10").unwrap(),
1362+
bip32::DerivationPath::from_str("m/2/9854/0/5/10").unwrap()
1363+
],
1364+
);
1365+
assert_eq!(
1366+
xprv,
1367+
get_multipath_xprv(&DescriptorSecretKey::MultiXPrv(xprv.clone()).to_string())
1368+
);
1369+
let xprv = get_multipath_xprv("tprv8ZgxMBicQKsPcwcD4gSnMti126ZiETsuX7qwrtMypr6FBwAP65puFn4v6c3jrN9VwtMRMph6nyT63NrfUL4C3nBzPcduzVSuHD7zbX2JKVc/2/<0;1;9854>/3456/9876/*");
1370+
assert_eq!(xprv.wildcard, Wildcard::Unhardened);
1371+
assert_eq!(
1372+
xprv.derivation_paths.paths(),
1373+
&vec![
1374+
bip32::DerivationPath::from_str("m/2/0/3456/9876").unwrap(),
1375+
bip32::DerivationPath::from_str("m/2/1/3456/9876").unwrap(),
1376+
bip32::DerivationPath::from_str("m/2/9854/3456/9876").unwrap()
1377+
],
1378+
);
1379+
assert_eq!(
1380+
xprv,
1381+
get_multipath_xprv(&DescriptorSecretKey::MultiXPrv(xprv.clone()).to_string())
1382+
);
1383+
let xprv = get_multipath_xprv("[abcdef00/0'/1']tprv8ZgxMBicQKsPcwcD4gSnMti126ZiETsuX7qwrtMypr6FBwAP65puFn4v6c3jrN9VwtMRMph6nyT63NrfUL4C3nBzPcduzVSuHD7zbX2JKVc/<0;1>/*");
1384+
assert_eq!(xprv.wildcard, Wildcard::Unhardened);
1385+
assert_eq!(
1386+
xprv.derivation_paths.paths(),
1387+
&vec![
1388+
bip32::DerivationPath::from_str("m/0").unwrap(),
1389+
bip32::DerivationPath::from_str("m/1").unwrap(),
1390+
],
1391+
);
1392+
assert_eq!(
1393+
xprv,
1394+
get_multipath_xprv(&DescriptorSecretKey::MultiXPrv(xprv.clone()).to_string())
1395+
);
1396+
let xprv = get_multipath_xprv("[abcdef00/0'/1']tprv8ZgxMBicQKsPcwcD4gSnMti126ZiETsuX7qwrtMypr6FBwAP65puFn4v6c3jrN9VwtMRMph6nyT63NrfUL4C3nBzPcduzVSuHD7zbX2JKVc/9478'/<0';1h>/8h/*'");
1397+
assert_eq!(xprv.wildcard, Wildcard::Hardened);
1398+
assert_eq!(
1399+
xprv.derivation_paths.paths(),
1400+
&vec![
1401+
bip32::DerivationPath::from_str("m/9478'/0'/8'").unwrap(),
1402+
bip32::DerivationPath::from_str("m/9478h/1h/8h").unwrap(),
1403+
],
1404+
);
1405+
assert_eq!(
1406+
xprv,
1407+
get_multipath_xprv(&DescriptorSecretKey::MultiXPrv(xprv.clone()).to_string())
1408+
);
1409+
let desc_key = DescriptorSecretKey::from_str("[abcdef00/0'/1']tprv8ZgxMBicQKsPcwcD4gSnMti126ZiETsuX7qwrtMypr6FBwAP65puFn4v6c3jrN9VwtMRMph6nyT63NrfUL4C3nBzPcduzVSuHD7zbX2JKVc/9478'/<0';1>/8h/*'").unwrap();
1410+
assert!(desc_key.to_public(&secp).is_err());
1411+
1412+
// It's invalid to:
1413+
// - Not have opening or closing brackets
1414+
// - Have multiple steps with different indexes
1415+
// - Only have one index within the brackets
1416+
DescriptorPublicKey::from_str("tpubDBrgjcxBxnXyL575sHdkpKohWu5qHKoQ7TJXKNrYznh5fVEGBv89hA8ENW7A8MFVpFUSvgLqc4Nj1WZcpePX6rrxviVtPowvMuGF5rdT2Vi/2/<0;1;42;9854").unwrap_err();
1417+
DescriptorPublicKey::from_str("tpubDBrgjcxBxnXyL575sHdkpKohWu5qHKoQ7TJXKNrYznh5fVEGBv89hA8ENW7A8MFVpFUSvgLqc4Nj1WZcpePX6rrxviVtPowvMuGF5rdT2Vi/2/0;1;42;9854>").unwrap_err();
1418+
DescriptorPublicKey::from_str("tpubDBrgjcxBxnXyL575sHdkpKohWu5qHKoQ7TJXKNrYznh5fVEGBv89hA8ENW7A8MFVpFUSvgLqc4Nj1WZcpePX6rrxviVtPowvMuGF5rdT2Vi/2/4/<0;1>/96/<0;1>").unwrap_err();
1419+
DescriptorPublicKey::from_str("tpubDBrgjcxBxnXyL575sHdkpKohWu5qHKoQ7TJXKNrYznh5fVEGBv89hA8ENW7A8MFVpFUSvgLqc4Nj1WZcpePX6rrxviVtPowvMuGF5rdT2Vi/2/4/<0>").unwrap_err();
1420+
DescriptorPublicKey::from_str("tpubDBrgjcxBxnXyL575sHdkpKohWu5qHKoQ7TJXKNrYznh5fVEGBv89hA8ENW7A8MFVpFUSvgLqc4Nj1WZcpePX6rrxviVtPowvMuGF5rdT2Vi/2/4/<0;>").unwrap_err();
1421+
DescriptorPublicKey::from_str("tpubDBrgjcxBxnXyL575sHdkpKohWu5qHKoQ7TJXKNrYznh5fVEGBv89hA8ENW7A8MFVpFUSvgLqc4Nj1WZcpePX6rrxviVtPowvMuGF5rdT2Vi/2/4/<;1>").unwrap_err();
1422+
DescriptorPublicKey::from_str("tpubDBrgjcxBxnXyL575sHdkpKohWu5qHKoQ7TJXKNrYznh5fVEGBv89hA8ENW7A8MFVpFUSvgLqc4Nj1WZcpePX6rrxviVtPowvMuGF5rdT2Vi/2/4/<0;1;>").unwrap_err();
1423+
}
12391424
}

0 commit comments

Comments
 (0)