use crate::error::TaprootError;
use secp256k1::{Secp256k1, XOnlyPublicKey as Secp256k1XOnlyPublicKey};
use std::fmt;
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
pub enum Parity {
Even,
Odd,
}
impl Parity {
pub fn to_u8(self) -> u8 {
match self {
Parity::Even => 0,
Parity::Odd => 1,
}
}
pub fn from_u8(value: u8) -> Result<Self, TaprootError> {
match value {
0 => Ok(Parity::Even),
1 => Ok(Parity::Odd),
_ => Err(TaprootError::InvalidParity),
}
}
}
impl From<secp256k1::Parity> for Parity {
fn from(p: secp256k1::Parity) -> Self {
match p {
secp256k1::Parity::Even => Parity::Even,
secp256k1::Parity::Odd => Parity::Odd,
}
}
}
impl From<Parity> for secp256k1::Parity {
fn from(p: Parity) -> Self {
match p {
Parity::Even => secp256k1::Parity::Even,
Parity::Odd => secp256k1::Parity::Odd,
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub struct XOnlyPublicKey {
inner: Secp256k1XOnlyPublicKey,
}
impl XOnlyPublicKey {
pub fn from_compressed(data: &[u8; 33]) -> Result<(Self, Parity), TaprootError> {
let pubkey = secp256k1::PublicKey::from_slice(data)
.map_err(|e| TaprootError::Secp256k1Error(e.to_string()))?;
let (xonly, parity) = pubkey.x_only_public_key();
Ok((Self { inner: xonly }, parity.into()))
}
pub fn from_slice(data: &[u8]) -> Result<Self, TaprootError> {
if data.len() != 32 {
return Err(TaprootError::InvalidLength {
expected: 32,
got: data.len(),
});
}
let inner = Secp256k1XOnlyPublicKey::from_slice(data)
.map_err(|e| TaprootError::Secp256k1Error(e.to_string()))?;
Ok(Self { inner })
}
pub fn from_bytes(data: [u8; 32]) -> Result<Self, TaprootError> {
Self::from_slice(&data)
}
pub fn serialize(&self) -> [u8; 32] {
self.inner.serialize()
}
pub fn as_bytes(&self) -> &[u8] {
unsafe {
std::slice::from_raw_parts(
&self.inner as *const _ as *const u8,
32,
)
}
}
pub fn tweak_add(
&self,
secp: &Secp256k1<secp256k1::All>,
tweak: &[u8; 32],
) -> Result<(Self, Parity), TaprootError> {
let scalar = secp256k1::Scalar::from_be_bytes(*tweak)
.map_err(|e| TaprootError::InvalidTweak(e.to_string()))?;
let (tweaked, parity) = self.inner.add_tweak(secp, &scalar)
.map_err(|e| TaprootError::InvalidTweak(e.to_string()))?;
Ok((Self { inner: tweaked }, parity.into()))
}
pub fn inner(&self) -> &Secp256k1XOnlyPublicKey {
&self.inner
}
pub fn from_inner(inner: Secp256k1XOnlyPublicKey) -> Self {
Self { inner }
}
pub fn to_public_key(&self) -> secp256k1::PublicKey {
self.inner.public_key(secp256k1::Parity::Even)
}
}
impl fmt::Debug for XOnlyPublicKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "XOnlyPublicKey({})", hex::encode(self.serialize()))
}
}
impl fmt::Display for XOnlyPublicKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", hex::encode(self.serialize()))
}
}
impl std::str::FromStr for XOnlyPublicKey {
type Err = TaprootError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let bytes = hex::decode(s)
.map_err(|_| TaprootError::InvalidXOnlyKey)?;
Self::from_slice(&bytes)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_xonly_from_compressed() {
let compressed = [
0x02, 0x79, 0xbe, 0x66, 0x7e, 0xf9, 0xdc, 0xbb, 0xac,
0x55, 0xa0, 0x62, 0x95, 0xce, 0x87, 0x0b, 0x07,
0x02, 0x9b, 0xfc, 0xdb, 0x2d, 0xce, 0x28, 0xd9,
0x59, 0xf2, 0x81, 0x5b, 0x16, 0xf8, 0x17, 0x98,
];
let (xonly, parity) = XOnlyPublicKey::from_compressed(&compressed).unwrap();
assert_eq!(parity, Parity::Even);
assert_eq!(xonly.serialize()[..], compressed[1..]);
}
#[test]
fn test_xonly_roundtrip() {
let bytes = [
0x79, 0xbe, 0x66, 0x7e, 0xf9, 0xdc, 0xbb, 0xac,
0x55, 0xa0, 0x62, 0x95, 0xce, 0x87, 0x0b, 0x07,
0x02, 0x9b, 0xfc, 0xdb, 0x2d, 0xce, 0x28, 0xd9,
0x59, 0xf2, 0x81, 0x5b, 0x16, 0xf8, 0x17, 0x98,
];
let xonly = XOnlyPublicKey::from_bytes(bytes).unwrap();
assert_eq!(xonly.serialize(), bytes);
}
#[test]
fn test_xonly_display_parse() {
let bytes = [
0x79, 0xbe, 0x66, 0x7e, 0xf9, 0xdc, 0xbb, 0xac,
0x55, 0xa0, 0x62, 0x95, 0xce, 0x87, 0x0b, 0x07,
0x02, 0x9b, 0xfc, 0xdb, 0x2d, 0xce, 0x28, 0xd9,
0x59, 0xf2, 0x81, 0x5b, 0x16, 0xf8, 0x17, 0x98,
];
let xonly = XOnlyPublicKey::from_bytes(bytes).unwrap();
let hex_str = xonly.to_string();
let parsed: XOnlyPublicKey = hex_str.parse().unwrap();
assert_eq!(xonly, parsed);
}
#[test]
fn test_parity_conversion() {
assert_eq!(Parity::Even.to_u8(), 0);
assert_eq!(Parity::Odd.to_u8(), 1);
assert_eq!(Parity::from_u8(0).unwrap(), Parity::Even);
assert_eq!(Parity::from_u8(1).unwrap(), Parity::Odd);
assert!(Parity::from_u8(2).is_err());
}
}