Describe the bug
When using one of the BIP(44,49,84) templates the "coin type" part of the key derivation path is always set to 0, which represents bitcoin mainnet, no matter what the type of the key(s) used are. These templates should use 1 if the type of the key is testnet, regtest, or signet.
(Originally reported on discord by marcosalejandro)
To Reproduce
// BIP44 `pkh(key/44'/{0,1}'/0'/{0,1}/*)`
#[test]
fn test_bip44_template_cointype() {
use bitcoin::util::bip32::ChildNumber::Hardened;
let xprvkey = bitcoin::util::bip32::ExtendedPrivKey::from_str("xprv9s21ZrQH143K2fpbqApQL69a4oKdGVnVN52R82Ft7d1pSqgKmajF62acJo3aMszZb6qQ22QsVECSFxvf9uyxFUvFYQMq3QbtwtRSMjLAhMf").unwrap();
println!("bitcoin prvkey: {}", xprvkey.network);
assert_eq!(Network::Bitcoin, xprvkey.network);
let xdesc = Bip44(xprvkey, KeychainKind::Internal).build().unwrap();
println!("xdesc = {:?}", xdesc);
if let ExtendedDescriptor::Pkh(pkh) = xdesc.0 {
let path : Vec<ChildNumber> = pkh.into_inner().full_derivation_path().into();
let purpose = path.get(0).unwrap();
println!("xdesc purpose = {:?}", purpose);
assert!(matches!(purpose, Hardened { index: 44 }));
let coin_type = path.get(1).unwrap();
println!("xdesc coin_type = {:?}", coin_type);
assert!(matches!(coin_type, Hardened { index: 0 }));
}
let tprvkey = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap();
println!("testnet prvkey: {}", tprvkey.network);
assert_eq!(Network::Testnet, tprvkey.network);
let tdesc = Bip44(tprvkey, KeychainKind::Internal).build().unwrap();
println!("tdesc = {:?}", tdesc);
if let ExtendedDescriptor::Pkh(pkh) = tdesc.0 {
let path : Vec<ChildNumber> = pkh.into_inner().full_derivation_path().into();
let purpose = path.get(0).unwrap();
println!("tdesc purpose = {:?}", purpose);
assert!(matches!(purpose, Hardened { index: 44 }));
let coin_type = path.get(1).unwrap();
println!("tdesc coin_type = {:?}", coin_type);
assert!(matches!(coin_type, Hardened { index: 1 }));
}
}
test output:
bitcoin prvkey: bitcoin
xdesc = (pkh(XPub(DescriptorXKey { origin: Some((65af6f2e, m/44'/0'/0')), xkey: ExtendedPubKey { network: Bitcoin, depth: 3, parent_fingerprint: 714b621c, child_number: Hardened { index: 0 }, public_key: PublicKey { compressed: true, key: PublicKey(c3ce4936cde277e08e4604bc3381520747dcdec0d8b3ee4b5e5428436a385a5a47456d0b8d75187bb488ce073dd4759a1738db9e356a5bdaaaaf5ae459f9ebfe) }, chain_code: f9fd1286c60681930bbc5e74c2769ac2898a3564502ebe5039905e010e2b5787 }, derivation_path: m/1, wildcard: Unhardened })), {XPub(DescriptorXKey { origin: Some((65af6f2e, m/44'/0'/0')), xkey: ExtendedPubKey { network: Bitcoin, depth: 3, parent_fingerprint: 714b621c, child_number: Hardened { index: 0 }, public_key: PublicKey { compressed: true, key: PublicKey(c3ce4936cde277e08e4604bc3381520747dcdec0d8b3ee4b5e5428436a385a5a47456d0b8d75187bb488ce073dd4759a1738db9e356a5bdaaaaf5ae459f9ebfe) }, chain_code: f9fd1286c60681930bbc5e74c2769ac2898a3564502ebe5039905e010e2b5787 }, derivation_path: m/1, wildcard: Unhardened }): XPrv(DescriptorXKey { origin: None, xkey: ExtendedPrivKey { network: Bitcoin, depth: 0, parent_fingerprint: 00000000, child_number: Normal { index: 0 }, private_key: [private key data], chain_code: 3d6a59ac46bf8fd55349efa02581a8f1439dea8a62fbfe4de3bdf7c4ba629578 }, derivation_path: m/44'/0'/0'/1, wildcard: Unhardened })}, {Bitcoin})
xdesc purpose = Hardened { index: 44 }
xdesc coin_type = Hardened { index: 0 }
testnet prvkey: testnet
tdesc = (pkh(XPub(DescriptorXKey { origin: Some((34b00776, m/44'/0'/0')), xkey: ExtendedPubKey { network: Testnet, depth: 3, parent_fingerprint: 1c00d808, child_number: Hardened { index: 0 }, public_key: PublicKey { compressed: true, key: PublicKey(f5f3776c7305f9adac93dc3b841d3018c54dd34c2e12cf45f6192daa679a99971c73da5604f0cc2a108a542095eef7e60587ca62d05d852f771bd5c2f8ad22db) }, chain_code: d7543f5ecbe9620f3249206b2fb085f4a905a8eb20f0d12c85407b5cd09ef515 }, derivation_path: m/1, wildcard: Unhardened })), {XPub(DescriptorXKey { origin: Some((34b00776, m/44'/0'/0')), xkey: ExtendedPubKey { network: Testnet, depth: 3, parent_fingerprint: 1c00d808, child_number: Hardened { index: 0 }, public_key: PublicKey { compressed: true, key: PublicKey(f5f3776c7305f9adac93dc3b841d3018c54dd34c2e12cf45f6192daa679a99971c73da5604f0cc2a108a542095eef7e60587ca62d05d852f771bd5c2f8ad22db) }, chain_code: d7543f5ecbe9620f3249206b2fb085f4a905a8eb20f0d12c85407b5cd09ef515 }, derivation_path: m/1, wildcard: Unhardened }): XPrv(DescriptorXKey { origin: None, xkey: ExtendedPrivKey { network: Testnet, depth: 0, parent_fingerprint: 00000000, child_number: Normal { index: 0 }, private_key: [private key data], chain_code: 07c7c6226c3cf2cd58bb416e0e124fa396a1559c95a3362c603c728c2c9de4f6 }, derivation_path: m/44'/0'/0'/1, wildcard: Unhardened })}, {Regtest, Signet, Testnet})
tdesc purpose = Hardened { index: 44 }
tdesc coin_type = Hardened { index: 0 }
assertion failed: matches!(coin_type, Hardened { index : 1 })
Expected behavior
According to BIP44 Coin Type for Testnet the ChildNumber at index 1 should be Hardened { index: 1 }.
Build environment
- BDK tag/commit: v0.17.0
- OS+version: any
- Rust/Cargo version: any
- Rust/Cargo target: any
Additional context
The Bip44, Bip49, and Bip84 macros all use the descriptor::template::expand_make_bipxx macro and this is where the coin type part of the derivation path is set.
Describe the bug
When using one of the BIP(44,49,84) templates the "coin type" part of the key derivation path is always set to 0, which represents bitcoin mainnet, no matter what the type of the key(s) used are. These templates should use 1 if the type of the key is testnet, regtest, or signet.
(Originally reported on discord by marcosalejandro)
To Reproduce
test output:
bitcoin prvkey: bitcoin xdesc = (pkh(XPub(DescriptorXKey { origin: Some((65af6f2e, m/44'/0'/0')), xkey: ExtendedPubKey { network: Bitcoin, depth: 3, parent_fingerprint: 714b621c, child_number: Hardened { index: 0 }, public_key: PublicKey { compressed: true, key: PublicKey(c3ce4936cde277e08e4604bc3381520747dcdec0d8b3ee4b5e5428436a385a5a47456d0b8d75187bb488ce073dd4759a1738db9e356a5bdaaaaf5ae459f9ebfe) }, chain_code: f9fd1286c60681930bbc5e74c2769ac2898a3564502ebe5039905e010e2b5787 }, derivation_path: m/1, wildcard: Unhardened })), {XPub(DescriptorXKey { origin: Some((65af6f2e, m/44'/0'/0')), xkey: ExtendedPubKey { network: Bitcoin, depth: 3, parent_fingerprint: 714b621c, child_number: Hardened { index: 0 }, public_key: PublicKey { compressed: true, key: PublicKey(c3ce4936cde277e08e4604bc3381520747dcdec0d8b3ee4b5e5428436a385a5a47456d0b8d75187bb488ce073dd4759a1738db9e356a5bdaaaaf5ae459f9ebfe) }, chain_code: f9fd1286c60681930bbc5e74c2769ac2898a3564502ebe5039905e010e2b5787 }, derivation_path: m/1, wildcard: Unhardened }): XPrv(DescriptorXKey { origin: None, xkey: ExtendedPrivKey { network: Bitcoin, depth: 0, parent_fingerprint: 00000000, child_number: Normal { index: 0 }, private_key: [private key data], chain_code: 3d6a59ac46bf8fd55349efa02581a8f1439dea8a62fbfe4de3bdf7c4ba629578 }, derivation_path: m/44'/0'/0'/1, wildcard: Unhardened })}, {Bitcoin}) xdesc purpose = Hardened { index: 44 } xdesc coin_type = Hardened { index: 0 } testnet prvkey: testnet tdesc = (pkh(XPub(DescriptorXKey { origin: Some((34b00776, m/44'/0'/0')), xkey: ExtendedPubKey { network: Testnet, depth: 3, parent_fingerprint: 1c00d808, child_number: Hardened { index: 0 }, public_key: PublicKey { compressed: true, key: PublicKey(f5f3776c7305f9adac93dc3b841d3018c54dd34c2e12cf45f6192daa679a99971c73da5604f0cc2a108a542095eef7e60587ca62d05d852f771bd5c2f8ad22db) }, chain_code: d7543f5ecbe9620f3249206b2fb085f4a905a8eb20f0d12c85407b5cd09ef515 }, derivation_path: m/1, wildcard: Unhardened })), {XPub(DescriptorXKey { origin: Some((34b00776, m/44'/0'/0')), xkey: ExtendedPubKey { network: Testnet, depth: 3, parent_fingerprint: 1c00d808, child_number: Hardened { index: 0 }, public_key: PublicKey { compressed: true, key: PublicKey(f5f3776c7305f9adac93dc3b841d3018c54dd34c2e12cf45f6192daa679a99971c73da5604f0cc2a108a542095eef7e60587ca62d05d852f771bd5c2f8ad22db) }, chain_code: d7543f5ecbe9620f3249206b2fb085f4a905a8eb20f0d12c85407b5cd09ef515 }, derivation_path: m/1, wildcard: Unhardened }): XPrv(DescriptorXKey { origin: None, xkey: ExtendedPrivKey { network: Testnet, depth: 0, parent_fingerprint: 00000000, child_number: Normal { index: 0 }, private_key: [private key data], chain_code: 07c7c6226c3cf2cd58bb416e0e124fa396a1559c95a3362c603c728c2c9de4f6 }, derivation_path: m/44'/0'/0'/1, wildcard: Unhardened })}, {Regtest, Signet, Testnet}) tdesc purpose = Hardened { index: 44 } tdesc coin_type = Hardened { index: 0 } assertion failed: matches!(coin_type, Hardened { index : 1 })Expected behavior
According to BIP44 Coin Type for
TestnettheChildNumberat index 1 should beHardened { index: 1 }.Build environment
Additional context
The Bip44, Bip49, and Bip84 macros all use the
descriptor::template::expand_make_bipxxmacro and this is where the coin type part of the derivation path is set.