use bitcoin::{
bip32::{DerivationPath, Xpriv, Xpub},
key::Secp256k1,
script::Builder,
Address, CompressedPublicKey, PublicKey, ScriptBuf,
};
use std::str::FromStr;
trait XDevive {
fn derive(&self, path: String) -> Result<(Xpub, Xpriv), crate::Error>;
fn multisig<const M: u8>(&self, paths: &[String]) -> Result<ScriptBuf, crate::Error>;
}
impl XDevive for Xpriv {
#[inline]
fn derive(&self, path: String) -> Result<(Xpub, Xpriv), crate::Error> {
let secp = Secp256k1::default();
let path = DerivationPath::from_str(&path)?;
let xpriv = self.derive_priv(&secp, &path)?;
let xpub = Xpub::from_priv(&secp, &xpriv);
Ok((xpub, xpriv))
}
fn multisig<const M: u8>(&self, paths: &[String]) -> Result<ScriptBuf, crate::Error> {
assert!(M <= paths.len() as u8 && paths.len() <= 15);
let secp = Secp256k1::default();
let mut pub_keys = paths
.iter()
.map(|path| {
let path = DerivationPath::from_str(path)?;
let priv_key = self.derive_priv(&secp, &path)?.to_priv();
Ok(PublicKey::from_private_key(&secp, &priv_key))
})
.collect::<Result<Vec<_>, crate::Error>>()?;
pub_keys.sort();
let script = pub_keys
.iter()
.fold(Builder::new().push_int(M as i64), |builder, key| {
builder.push_key(key)
})
.push_int(pub_keys.len() as i64)
.push_opcode(bitcoin::opcodes::all::OP_CHECKMULTISIG)
.into_script();
Ok(script)
}
}
type DeriveResult = Result<(String, String), crate::Error>;
#[cfg(not(feature = "testnet"))]
const COIN: u8 = 0;
#[cfg(feature = "testnet")]
const COIN: u8 = 1;
pub trait Bip44 {
fn bip44_account(&self, account: u32) -> DeriveResult;
fn bip44_wallet(&self, account: u32, index: u32) -> DeriveResult;
fn bip44_multisig<const M: u8, const N: u8>(&self, account: u32, index: u32) -> DeriveResult;
}
impl Bip44 for Xpriv {
fn bip44_account(&self, account: u32) -> DeriveResult {
self.derive(format!("m/44'/{COIN}'/{account}'"))
.map(|(xpub, xpriv)| (xpub.to_string(), xpriv.to_string()))
}
fn bip44_wallet(&self, account: u32, index: u32) -> DeriveResult {
self.derive(format!("m/44'/{COIN}'/{account}'/0/{index}'"))
.map(|(xpub, xpriv)| {
(
Address::p2pkh(CompressedPublicKey(xpub.public_key), crate::NETWORK)
.to_string(),
xpriv.to_priv().to_wif(),
)
})
}
fn bip44_multisig<const M: u8, const N: u8>(&self, account: u32, index: u32) -> DeriveResult {
let paths = (account..account + N as u32)
.map(|account: u32| format!("m/44'/{COIN}'/{account}'/0/{index}"))
.collect::<Vec<_>>();
let script = self.multisig::<M>(paths.as_slice())?;
Ok((
Address::p2sh(&script, crate::NETWORK)?.to_string(),
script.to_hex_string(),
))
}
}
pub trait Bip49 {
fn bip49_account(&self, account: u32) -> DeriveResult;
fn bip49_wallet(&self, account: u32, index: u32) -> DeriveResult;
fn bip49_multisig<const M: u8, const N: u8>(&self, account: u32, index: u32) -> DeriveResult;
}
impl Bip49 for Xpriv {
fn bip49_account(&self, account: u32) -> DeriveResult {
self.derive(format!("m/49'/{COIN}'/{account}'"))
.map(|(xpub, xpriv)| (xpub.to_ypub(), xpriv.to_ypriv()))
}
fn bip49_wallet(&self, account: u32, index: u32) -> DeriveResult {
self.derive(format!("m/49'/{COIN}'/{account}'/0/{index}'"))
.map(|(xpub, xpriv)| {
(
Address::p2shwpkh(&CompressedPublicKey(xpub.public_key), crate::NETWORK)
.to_string(),
xpriv.to_priv().to_wif(),
)
})
}
fn bip49_multisig<const M: u8, const N: u8>(&self, account: u32, index: u32) -> DeriveResult {
assert!(M <= N && N <= 15);
let paths = (account..account + N as u32)
.map(|account: u32| format!("m/49'/{COIN}'/{account}'/0/{index}"))
.collect::<Vec<_>>();
let script = self.multisig::<M>(paths.as_slice())?;
Ok((
Address::p2sh(&script, crate::NETWORK)?.to_string(),
script.to_hex_string(),
))
}
}
pub trait Bip84 {
fn bip84_account(&self, account: u32) -> DeriveResult;
fn bip84_wallet(&self, account: u32, index: u32) -> DeriveResult;
fn bip84_multisig<const M: u8, const N: u8>(&self, account: u32, index: u32) -> DeriveResult;
}
impl Bip84 for Xpriv {
fn bip84_account(&self, account: u32) -> DeriveResult {
self.derive(format!("m/84'/{COIN}'/{account}'"))
.map(|(xpub, xpriv)| (xpub.to_zpub(), xpriv.to_zpriv()))
}
fn bip84_wallet(&self, account: u32, index: u32) -> DeriveResult {
let network = match crate::NETWORK.is_mainnet() {
true => bitcoin::Network::Bitcoin,
false => bitcoin::Network::Testnet,
};
self.derive(format!("m/84'/{COIN}'/{account}'/0/{index}'"))
.map(|(xpub, xpriv)| {
(
Address::p2wpkh(&CompressedPublicKey(xpub.public_key), network).to_string(),
xpriv.to_priv().to_wif(),
)
})
}
fn bip84_multisig<const M: u8, const N: u8>(&self, account: u32, index: u32) -> DeriveResult {
assert!(M <= N && N <= 15);
let paths = (account..account + N as u32)
.map(|account: u32| format!("m/84'/{COIN}'/{account}'/0/{index}"))
.collect::<Vec<_>>();
let script = self.multisig::<M>(paths.as_slice())?;
Ok((
Address::p2sh(&script, crate::NETWORK)?.to_string(),
script.to_hex_string(),
))
}
}
const BIP49_VERSION_BYTES_MAINNET_PRIVATE: u32 = 0x049d7878; const BIP49_VERSION_BYTES_MAINNET_PUBLIC: u32 = 0x049d7cb2; const BIP49_VERSION_BYTES_TESTNET_PRIVATE: u32 = 0x044a4e28; const BIP49_VERSION_BYTES_TESTNET_PUBLIC: u32 = 0x044a5262;
const BIP84_VERSION_BYTES_MAINNET_PRIVATE: u32 = 0x04b2430c; const BIP84_VERSION_BYTES_MAINNET_PUBLIC: u32 = 0x04b24746; const BIP84_VERSION_BYTES_TESTNET_PRIVATE: u32 = 0x045f18bc; const BIP84_VERSION_BYTES_TESTNET_PUBLIC: u32 = 0x045f1cf6;
trait XprivEncode {
fn ext_encode<const PRE: u32>(&self) -> String;
fn to_ypriv(&self) -> String;
fn to_zpriv(&self) -> String;
}
impl XprivEncode for Xpriv {
#[inline]
fn ext_encode<const PRE: u32>(&self) -> String {
let data = [&PRE.to_be_bytes(), &self.encode()[4..]].concat();
bitcoin::base58::encode_check(&data[..])
}
#[inline]
fn to_ypriv(&self) -> String {
match crate::NETWORK.is_mainnet() {
true => self.ext_encode::<BIP49_VERSION_BYTES_MAINNET_PRIVATE>(),
false => self.ext_encode::<BIP49_VERSION_BYTES_TESTNET_PRIVATE>(),
}
}
#[inline]
fn to_zpriv(&self) -> String {
match crate::NETWORK.is_mainnet() {
true => self.ext_encode::<BIP84_VERSION_BYTES_MAINNET_PRIVATE>(),
false => self.ext_encode::<BIP84_VERSION_BYTES_TESTNET_PRIVATE>(),
}
}
}
trait XpubEncode {
fn ext_encode<const PRE: u32>(&self) -> String;
fn to_ypub(&self) -> String;
fn to_zpub(&self) -> String;
}
impl XpubEncode for Xpub {
#[inline]
fn ext_encode<const PRE: u32>(&self) -> String {
let data = [&PRE.to_be_bytes(), &self.encode()[4..]].concat();
bitcoin::base58::encode_check(&data[..])
}
#[inline]
fn to_ypub(&self) -> String {
match crate::NETWORK.is_mainnet() {
true => self.ext_encode::<BIP49_VERSION_BYTES_MAINNET_PUBLIC>(),
false => self.ext_encode::<BIP49_VERSION_BYTES_TESTNET_PUBLIC>(),
}
}
#[inline]
fn to_zpub(&self) -> String {
match crate::NETWORK.is_mainnet() {
true => self.ext_encode::<BIP84_VERSION_BYTES_MAINNET_PUBLIC>(),
false => self.ext_encode::<BIP84_VERSION_BYTES_TESTNET_PUBLIC>(),
}
}
}