use crate::descriptor::Descriptor;
use crate::error::DescriptorError;
use crate::script::{generate_script_pubkey, ScriptType};
use rustywallet_address::Network;
pub type AddressString = String;
pub fn derive_address(
descriptor: &Descriptor,
network: Network,
index: u32,
) -> Result<AddressString, DescriptorError> {
let script = generate_script_pubkey(descriptor, index)?;
match script.script_type() {
ScriptType::P2pk => {
Err(DescriptorError::AddressError(
"P2PK scripts don't have standard addresses".into(),
))
}
ScriptType::P2pkh => {
if script.as_bytes().len() != 25 {
return Err(DescriptorError::ScriptError("Invalid P2PKH script".into()));
}
let mut pubkey_hash = [0u8; 20];
pubkey_hash.copy_from_slice(&script.as_bytes()[3..23]);
let version = match network {
Network::BitcoinMainnet => 0x00,
Network::BitcoinTestnet => 0x6f,
_ => return Err(DescriptorError::AddressError("Unsupported network for P2PKH".into())),
};
let address = base58check_encode(version, &pubkey_hash);
Ok(address)
}
ScriptType::P2sh => {
if script.as_bytes().len() != 23 {
return Err(DescriptorError::ScriptError("Invalid P2SH script".into()));
}
let mut script_hash = [0u8; 20];
script_hash.copy_from_slice(&script.as_bytes()[2..22]);
let version = match network {
Network::BitcoinMainnet => 0x05,
Network::BitcoinTestnet => 0xc4,
_ => return Err(DescriptorError::AddressError("Unsupported network for P2SH".into())),
};
let address = base58check_encode(version, &script_hash);
Ok(address)
}
ScriptType::P2wpkh => {
if script.as_bytes().len() != 22 {
return Err(DescriptorError::ScriptError("Invalid P2WPKH script".into()));
}
let program = &script.as_bytes()[2..22];
let hrp = match network {
Network::BitcoinMainnet => "bc",
Network::BitcoinTestnet => "tb",
_ => return Err(DescriptorError::AddressError("Unsupported network for P2WPKH".into())),
};
let address = bech32_encode(hrp, 0, program)
.map_err(DescriptorError::AddressError)?;
Ok(address)
}
ScriptType::P2wsh => {
if script.as_bytes().len() != 34 {
return Err(DescriptorError::ScriptError("Invalid P2WSH script".into()));
}
let program = &script.as_bytes()[2..34];
let hrp = match network {
Network::BitcoinMainnet => "bc",
Network::BitcoinTestnet => "tb",
_ => return Err(DescriptorError::AddressError("Unsupported network for P2WSH".into())),
};
let address = bech32_encode(hrp, 0, program)
.map_err(DescriptorError::AddressError)?;
Ok(address)
}
ScriptType::P2tr => {
if script.as_bytes().len() != 34 {
return Err(DescriptorError::ScriptError("Invalid P2TR script".into()));
}
let program = &script.as_bytes()[2..34];
let hrp = match network {
Network::BitcoinMainnet => "bc",
Network::BitcoinTestnet => "tb",
_ => return Err(DescriptorError::AddressError("Unsupported network for P2TR".into())),
};
let address = bech32m_encode(hrp, 1, program)
.map_err(DescriptorError::AddressError)?;
Ok(address)
}
}
}
pub fn derive_addresses(
descriptor: &Descriptor,
network: Network,
start: u32,
count: u32,
) -> Result<Vec<AddressString>, DescriptorError> {
let mut addresses = Vec::with_capacity(count as usize);
for i in start..start + count {
let addr = derive_address(descriptor, network, i)?;
addresses.push(addr);
}
Ok(addresses)
}
fn base58check_encode(version: u8, data: &[u8]) -> String {
use sha2::{Sha256, Digest};
let mut payload = Vec::with_capacity(1 + data.len() + 4);
payload.push(version);
payload.extend_from_slice(data);
let hash1 = Sha256::digest(&payload);
let hash2 = Sha256::digest(hash1);
payload.extend_from_slice(&hash2[..4]);
bs58::encode(payload).into_string()
}
fn bech32_encode(hrp: &str, version: u8, program: &[u8]) -> Result<String, String> {
use bech32::{Bech32, Hrp};
let hrp = Hrp::parse(hrp).map_err(|e| e.to_string())?;
let mut data = Vec::with_capacity(1 + program.len() * 8 / 5 + 1);
data.push(version);
let converted = convert_bits(program, 8, 5, true)
.map_err(|e| e.to_string())?;
data.extend(converted);
bech32::encode::<Bech32>(hrp, &data)
.map_err(|e| e.to_string())
}
fn bech32m_encode(hrp: &str, version: u8, program: &[u8]) -> Result<String, String> {
use bech32::{Bech32m, Hrp};
let hrp = Hrp::parse(hrp).map_err(|e| e.to_string())?;
let mut data = Vec::with_capacity(1 + program.len() * 8 / 5 + 1);
data.push(version);
let converted = convert_bits(program, 8, 5, true)
.map_err(|e| e.to_string())?;
data.extend(converted);
bech32::encode::<Bech32m>(hrp, &data)
.map_err(|e| e.to_string())
}
fn convert_bits(data: &[u8], from_bits: u32, to_bits: u32, pad: bool) -> Result<Vec<u8>, String> {
let mut acc: u32 = 0;
let mut bits: u32 = 0;
let mut ret = Vec::new();
let maxv: u32 = (1 << to_bits) - 1;
for &value in data {
let value = value as u32;
if (value >> from_bits) != 0 {
return Err("Invalid value".into());
}
acc = (acc << from_bits) | value;
bits += from_bits;
while bits >= to_bits {
bits -= to_bits;
ret.push(((acc >> bits) & maxv) as u8);
}
}
if pad {
if bits > 0 {
ret.push(((acc << (to_bits - bits)) & maxv) as u8);
}
} else if bits >= from_bits || ((acc << (to_bits - bits)) & maxv) != 0 {
return Err("Invalid padding".into());
}
Ok(ret)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::descriptor::Descriptor;
#[test]
fn test_derive_pkh_address() {
let desc = "pkh(02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5)";
let descriptor = Descriptor::parse(desc).unwrap();
let address = derive_address(&descriptor, Network::BitcoinMainnet, 0).unwrap();
assert!(address.starts_with('1'));
}
#[test]
fn test_derive_wpkh_address() {
let desc = "wpkh(02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5)";
let descriptor = Descriptor::parse(desc).unwrap();
let address = derive_address(&descriptor, Network::BitcoinMainnet, 0).unwrap();
assert!(address.starts_with("bc1q"));
}
#[test]
fn test_derive_sh_wpkh_address() {
let desc = "sh(wpkh(02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5))";
let descriptor = Descriptor::parse(desc).unwrap();
let address = derive_address(&descriptor, Network::BitcoinMainnet, 0).unwrap();
assert!(address.starts_with('3'));
}
#[test]
fn test_derive_tr_address() {
let desc = "tr(02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5)";
let descriptor = Descriptor::parse(desc).unwrap();
let address = derive_address(&descriptor, Network::BitcoinMainnet, 0).unwrap();
assert!(address.starts_with("bc1p") || address.starts_with("bc1"));
}
#[test]
fn test_derive_multiple_addresses() {
let desc = "wpkh(xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/0/*)";
let descriptor = Descriptor::parse(desc).unwrap();
let addresses = derive_addresses(&descriptor, Network::BitcoinMainnet, 0, 5).unwrap();
assert_eq!(addresses.len(), 5);
for i in 0..addresses.len() {
for j in i + 1..addresses.len() {
assert_ne!(addresses[i], addresses[j]);
}
}
}
}