use crate::{Descriptors, Electrum2DescriptorError, ElectrumExtendedKey};
use bitcoin::base58;
use bitcoin::bip32::{ChainCode, ChildNumber, ExtendedPrivKey, Fingerprint};
use bitcoin::secp256k1;
use bitcoin::Network;
use std::convert::TryInto;
use std::str::FromStr;
pub struct ElectrumExtendedPrivKey {
xprv: ExtendedPrivKey,
kind: String,
}
type SentinelMap = Vec<([u8; 4], Network, String)>;
fn initialize_sentinels() -> SentinelMap {
vec![
(
[0x04u8, 0x35, 0x83, 0x94],
Network::Testnet,
"pkh".to_string(),
), (
[0x04u8, 0x4a, 0x4e, 0x28],
Network::Testnet,
"sh(wpkh".to_string(),
), (
[0x02u8, 0x42, 0x85, 0xb5],
Network::Testnet,
"sh(wsh".to_string(),
), (
[0x04u8, 0x5f, 0x18, 0xbc],
Network::Testnet,
"wpkh".to_string(),
), (
[0x02u8, 0x57, 0x50, 0x48],
Network::Testnet,
"wsh".to_string(),
), (
[0x04u8, 0x88, 0xad, 0xE4],
Network::Bitcoin,
"pkh".to_string(),
), (
[0x04u8, 0x9d, 0x78, 0x78],
Network::Bitcoin,
"sh(wpkh".to_string(),
), (
[0x02u8, 0x95, 0xb0, 0x05],
Network::Bitcoin,
"sh(wsh".to_string(),
), (
[0x04u8, 0xb2, 0x43, 0x0c],
Network::Bitcoin,
"wpkh".to_string(),
), (
[0x02u8, 0xaa, 0x7a, 0x99],
Network::Bitcoin,
"wsh".to_string(),
), ]
}
impl FromStr for ElectrumExtendedPrivKey {
type Err = Electrum2DescriptorError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let data = base58::decode_check(s)?;
if data.len() != 78 {
return Err(Electrum2DescriptorError::Base58Error(
base58::Error::InvalidLength(data.len()),
));
}
let cn_int = u32::from_be_bytes(data[9..13].try_into().unwrap());
let child_number: ChildNumber = ChildNumber::from(cn_int);
let (network, kind) = match_electrum_xprv(&data[0..4])?;
let key = secp256k1::SecretKey::from_slice(&data[46..78])?;
let xprv = ExtendedPrivKey {
network,
depth: data[4],
parent_fingerprint: Fingerprint::from(&data[5..9].try_into().unwrap()),
child_number,
chain_code: ChainCode::from(&data[13..45].try_into().unwrap()),
private_key: key,
};
Ok(ElectrumExtendedPrivKey { xprv, kind })
}
}
impl ElectrumExtendedKey for ElectrumExtendedPrivKey {
fn kind(&self) -> &str {
&self.kind
}
fn xkey_str(&self) -> String {
self.xprv.to_string()
}
fn to_descriptors(&self) -> Descriptors {
let xprv = self.xprv.to_string();
let closing_parenthesis = if self.kind.contains('(') { ")" } else { "" };
let [external, change] =
[0, 1].map(|i| format!("{}({}/{}/*){}", self.kind, xprv, i, closing_parenthesis));
Descriptors { external, change }
}
}
impl ElectrumExtendedPrivKey {
pub fn new(xprv: ExtendedPrivKey, kind: String) -> Self {
ElectrumExtendedPrivKey { xprv, kind }
}
pub fn xprv(&self) -> &ExtendedPrivKey {
&self.xprv
}
pub fn electrum_xprv(&self) -> Result<String, Electrum2DescriptorError> {
let sentinels = initialize_sentinels();
let sentinel = sentinels
.iter()
.find(|sent| sent.1 == self.xprv.network && sent.2 == self.kind)
.ok_or_else(|| Electrum2DescriptorError::UnknownType)?;
let mut data = Vec::from(&sentinel.0[..]);
data.push(self.xprv.depth);
data.extend(self.xprv.parent_fingerprint.as_bytes());
let child_number: u32 = self.xprv.child_number.into();
data.extend(child_number.to_be_bytes());
data.extend(self.xprv.chain_code.as_bytes());
data.push(0u8);
data.extend(self.xprv.private_key.as_ref());
if data.len() != 78 {
return Err(Electrum2DescriptorError::Base58Error(
base58::Error::InvalidLength(data.len()),
));
}
Ok(base58::encode_check(&data))
}
}
fn match_electrum_xprv(version: &[u8]) -> Result<(Network, String), base58::Error> {
let sentinels = initialize_sentinels();
let sentinel = sentinels
.iter()
.find(|sent| sent.0 == version)
.ok_or_else(|| {
base58::Error::InvalidExtendedKeyVersion(version[0..4].try_into().unwrap())
})?;
Ok((sentinel.1, sentinel.2.clone()))
}
#[cfg(test)]
mod tests {
use super::*;
use std::str::FromStr;
#[test]
fn test_vprv_from_electrum() {
let electrum_xprv = ElectrumExtendedPrivKey::from_str("yprvAHwhK6RbpuS3dgCYHM5jc2ZvEKd7Bi61u9FVhYMpgMSuZS613T1xxQeKTffhrHY79hZ5PsskBjcc6C2V7DrnsMsNaGDaWev3GLRQRgV7hxF").unwrap();
assert_eq!(electrum_xprv.xprv.to_string(),"xprv9y7S1RkggDtZnP1RSzJ7PwUR4MUfF66Wz2jGv9TwJM52WLGmnnrQLLzBSTi7rNtBk4SGeQHBj5G4CuQvPXSn58BmhvX9vk6YzcMm37VuNYD");
assert_eq!(electrum_xprv.kind, "sh(wpkh");
let descriptors = electrum_xprv.to_descriptors();
assert_eq!(descriptors.external, "sh(wpkh(xprv9y7S1RkggDtZnP1RSzJ7PwUR4MUfF66Wz2jGv9TwJM52WLGmnnrQLLzBSTi7rNtBk4SGeQHBj5G4CuQvPXSn58BmhvX9vk6YzcMm37VuNYD/0/*))");
assert_eq!(descriptors.change, "sh(wpkh(xprv9y7S1RkggDtZnP1RSzJ7PwUR4MUfF66Wz2jGv9TwJM52WLGmnnrQLLzBSTi7rNtBk4SGeQHBj5G4CuQvPXSn58BmhvX9vk6YzcMm37VuNYD/1/*))");
let xprv = electrum_xprv.xprv();
assert_eq!(xprv.to_string(), "xprv9y7S1RkggDtZnP1RSzJ7PwUR4MUfF66Wz2jGv9TwJM52WLGmnnrQLLzBSTi7rNtBk4SGeQHBj5G4CuQvPXSn58BmhvX9vk6YzcMm37VuNYD");
}
#[test]
fn test_vprv_to_electrum() {
let electrum_xprv = ElectrumExtendedPrivKey::new(
ExtendedPrivKey::from_str("xprv9y7S1RkggDtZnP1RSzJ7PwUR4MUfF66Wz2jGv9TwJM52WLGmnnrQLLzBSTi7rNtBk4SGeQHBj5G4CuQvPXSn58BmhvX9vk6YzcMm37VuNYD").unwrap(),
"sh(wpkh".to_string(),
);
assert_eq!(electrum_xprv.electrum_xprv().unwrap(), "yprvAHwhK6RbpuS3dgCYHM5jc2ZvEKd7Bi61u9FVhYMpgMSuZS613T1xxQeKTffhrHY79hZ5PsskBjcc6C2V7DrnsMsNaGDaWev3GLRQRgV7hxF");
}
#[test]
fn test_vprv_roundtrip() {
let elxprv = "yprvAHwhK6RbpuS3dgCYHM5jc2ZvEKd7Bi61u9FVhYMpgMSuZS613T1xxQeKTffhrHY79hZ5PsskBjcc6C2V7DrnsMsNaGDaWev3GLRQRgV7hxF";
let electrum_xprv = ElectrumExtendedPrivKey::from_str(elxprv).unwrap();
assert_eq!(electrum_xprv.electrum_xprv().unwrap(), elxprv);
assert_ne!(electrum_xprv.xprv.to_string(), elxprv);
}
}