use super::*;
pub const XPUB_DATA_SIZE: usize = 78;
#[derive(Clone)]
pub struct SecpExtPublicKey {
pub(super) depth: u8,
pub(super) parent_fingerprint: Vec<u8>,
pub(super) idx: u32,
pub(super) chain_code: ChainCode,
pub(super) pk: SecpPublicKey,
}
impl SecpExtPublicKey {
pub(crate) fn cook_new<F: Fn(&mut HmacSha512)>(&self, idx: u32, recipe: F) -> Self {
let parent = self;
let salt = &parent.chain_code.to_bytes();
let mut hasher = <HmacSha512 as KeyInit>::new_from_slice(salt).unwrap();
recipe(&mut hasher);
let hash_bytes = hasher.finalize().into_bytes();
let sk_bytes = &hash_bytes[..PRIVATE_KEY_SIZE];
let cc_bytes = &hash_bytes[PRIVATE_KEY_SIZE..];
let depth = parent.depth + 1;
let parent_pk = parent.public_key();
let hash = hash160(parent_pk.to_bytes());
let parent_fingerprint = Vec::from(&hash[..4]);
let chain_code = ChainCode::from_bytes(cc_bytes).unwrap();
let pk = (&parent.pk + sk_bytes)
.expect("We should have implemented that loop in the BIP32 specs");
Self { depth, parent_fingerprint, idx, chain_code, pk }
}
pub fn to_xpub(&self, version: &[u8; BIP32_VERSION_PREFIX_SIZE]) -> String {
let mut res = Vec::with_capacity(XPUB_DATA_SIZE);
res.extend_from_slice(version);
res.push(self.depth);
res.extend_from_slice(&self.parent_fingerprint);
res.extend_from_slice(&self.idx.to_be_bytes());
res.extend_from_slice(&self.chain_code.to_bytes());
res.extend_from_slice(&self.pk.to_bytes());
to_base58check(res)
}
pub fn from_xpub(xprv: &str, prefix: &[u8; BIP32_VERSION_PREFIX_SIZE]) -> Result<Self> {
let data = from_base58check(xprv)?;
ensure!(data.len() == XPUB_DATA_SIZE, "Length of data must be {}", XPUB_DATA_SIZE);
debug_assert_eq!(BIP32_VERSION_PREFIX_SIZE, 4);
let actual_prefix = &data[0..BIP32_VERSION_PREFIX_SIZE];
ensure!(
actual_prefix == prefix,
"Invalid network prefix found: {}",
hex::encode(actual_prefix)
);
let depth = data[4];
let parent_fingerprint = data[5..9].to_vec();
let idx = {
let mut idx_bytes = [0u8; 4];
idx_bytes.copy_from_slice(&data[9..13]);
u32::from_be_bytes(idx_bytes)
};
let chain_code = {
let chain_code_bytes = &data[13..45];
ChainCode::from_bytes(chain_code_bytes)?
};
let pk = {
let pk_bytes = &data[45..78];
SecpPublicKey::from_bytes(pk_bytes)?
};
Ok(Self { depth, parent_fingerprint, idx, chain_code, pk })
}
}
impl ExtendedPublicKey<Secp256k1> for SecpExtPublicKey {
fn derive_normal_child(&self, idx: i32) -> Result<SecpExtPublicKey> {
ensure!(idx >= 0, "Derivation index cannot be negative");
let idx = idx as u32;
let xpub = self.cook_new(idx, |hasher: &mut HmacSha512| {
hasher.update(&self.pk.to_bytes());
hasher.update(&idx.to_be_bytes());
});
Ok(xpub)
}
fn public_key(&self) -> SecpPublicKey {
self.pk.clone()
}
}