use crate::error::TaprootError;
use crate::tagged_hash::TapNodeHash;
use crate::taptree::TapTree;
use crate::tweak::tweak_public_key;
use crate::xonly::{Parity, XOnlyPublicKey};
use bech32::Hrp;
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum Network {
Mainnet,
Testnet,
Signet,
Regtest,
}
impl Network {
pub fn hrp(&self) -> &'static str {
match self {
Network::Mainnet => "bc",
Network::Testnet | Network::Signet => "tb",
Network::Regtest => "bcrt",
}
}
}
#[derive(Clone, Debug)]
pub struct TaprootOutput {
pub output_key: XOnlyPublicKey,
pub parity: Parity,
pub internal_key: XOnlyPublicKey,
pub merkle_root: Option<TapNodeHash>,
}
impl TaprootOutput {
pub fn key_path_only(internal_key: XOnlyPublicKey) -> Result<Self, TaprootError> {
let (output_key, parity) = tweak_public_key(&internal_key, None)?;
Ok(Self {
output_key,
parity,
internal_key,
merkle_root: None,
})
}
pub fn with_script_tree(
internal_key: XOnlyPublicKey,
tree: &TapTree,
) -> Result<Self, TaprootError> {
let merkle_root = tree.root_hash();
let (output_key, parity) = tweak_public_key(&internal_key, Some(&merkle_root))?;
Ok(Self {
output_key,
parity,
internal_key,
merkle_root: Some(merkle_root),
})
}
pub fn from_output_key(output_key: XOnlyPublicKey) -> Self {
Self {
output_key,
parity: Parity::Even, internal_key: output_key, merkle_root: None,
}
}
pub fn script_pubkey(&self) -> Vec<u8> {
let mut script = Vec::with_capacity(34);
script.push(0x51); script.push(0x20); script.extend_from_slice(&self.output_key.serialize());
script
}
pub fn address(&self, network: Network) -> Result<String, TaprootError> {
let hrp = Hrp::parse(network.hrp())
.map_err(|e| TaprootError::InvalidScript(e.to_string()))?;
let mut data = Vec::with_capacity(33);
data.push(1); data.extend_from_slice(&self.output_key.serialize());
bech32::segwit::encode(hrp, bech32::segwit::VERSION_1, &self.output_key.serialize())
.map_err(|e| TaprootError::InvalidScript(e.to_string()))
}
pub fn is_key_path_only(&self) -> bool {
self.merkle_root.is_none()
}
}
pub fn parse_address(address: &str) -> Result<XOnlyPublicKey, TaprootError> {
let (_hrp, version, program) = bech32::segwit::decode(address)
.map_err(|e| TaprootError::InvalidScript(e.to_string()))?;
if version != bech32::segwit::VERSION_1 {
return Err(TaprootError::InvalidScript(format!(
"Expected witness version 1, got {:?}",
version
)));
}
if program.len() != 32 {
return Err(TaprootError::InvalidLength {
expected: 32,
got: program.len(),
});
}
XOnlyPublicKey::from_slice(&program)
}
pub fn is_p2tr_address(address: &str) -> bool {
parse_address(address).is_ok()
}
pub fn create_address(pubkey: &XOnlyPublicKey, network: Network) -> Result<String, TaprootError> {
let output = TaprootOutput::from_output_key(*pubkey);
output.address(network)
}
#[cfg(test)]
mod tests {
use super::*;
use secp256k1::{Secp256k1, SecretKey};
fn get_test_internal_key() -> XOnlyPublicKey {
let secp = Secp256k1::new();
let secret = [
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
];
let sk = SecretKey::from_slice(&secret).unwrap();
let pk = sk.public_key(&secp);
let (xonly, _) = pk.x_only_public_key();
XOnlyPublicKey::from_inner(xonly)
}
#[test]
fn test_key_path_only() {
let internal_key = get_test_internal_key();
let output = TaprootOutput::key_path_only(internal_key).unwrap();
assert!(output.is_key_path_only());
assert!(output.merkle_root.is_none());
assert_ne!(output.output_key, internal_key); }
#[test]
fn test_script_pubkey() {
let internal_key = get_test_internal_key();
let output = TaprootOutput::key_path_only(internal_key).unwrap();
let script = output.script_pubkey();
assert_eq!(script.len(), 34);
assert_eq!(script[0], 0x51); assert_eq!(script[1], 0x20); }
#[test]
fn test_address_mainnet() {
let internal_key = get_test_internal_key();
let output = TaprootOutput::key_path_only(internal_key).unwrap();
let address = output.address(Network::Mainnet).unwrap();
assert!(address.starts_with("bc1p"));
}
#[test]
fn test_address_testnet() {
let internal_key = get_test_internal_key();
let output = TaprootOutput::key_path_only(internal_key).unwrap();
let address = output.address(Network::Testnet).unwrap();
assert!(address.starts_with("tb1p"));
}
#[test]
fn test_address_roundtrip() {
let internal_key = get_test_internal_key();
let output = TaprootOutput::key_path_only(internal_key).unwrap();
let address = output.address(Network::Mainnet).unwrap();
let parsed = parse_address(&address).unwrap();
assert_eq!(output.output_key, parsed);
}
#[test]
fn test_with_script_tree() {
use crate::taptree::two_leaf_tree;
let internal_key = get_test_internal_key();
let tree = two_leaf_tree(vec![0x51], vec![0x52]);
let output = TaprootOutput::with_script_tree(internal_key, &tree).unwrap();
assert!(!output.is_key_path_only());
assert!(output.merkle_root.is_some());
}
#[test]
fn test_is_p2tr_address() {
let internal_key = get_test_internal_key();
let output = TaprootOutput::key_path_only(internal_key).unwrap();
let address = output.address(Network::Mainnet).unwrap();
assert!(is_p2tr_address(&address));
assert!(!is_p2tr_address("bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4")); assert!(!is_p2tr_address("invalid"));
}
}