rustywallet_taproot/
xonly.rs

1//! X-only public keys (BIP340)
2//!
3//! X-only public keys are 32-byte representations that only include the x-coordinate.
4
5use crate::error::TaprootError;
6use secp256k1::{Secp256k1, XOnlyPublicKey as Secp256k1XOnlyPublicKey};
7use std::fmt;
8
9/// Parity of the y-coordinate
10#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
11pub enum Parity {
12    /// Even y-coordinate
13    Even,
14    /// Odd y-coordinate
15    Odd,
16}
17
18impl Parity {
19    /// Convert to u8 (0 for even, 1 for odd)
20    pub fn to_u8(self) -> u8 {
21        match self {
22            Parity::Even => 0,
23            Parity::Odd => 1,
24        }
25    }
26
27    /// Create from u8
28    pub fn from_u8(value: u8) -> Result<Self, TaprootError> {
29        match value {
30            0 => Ok(Parity::Even),
31            1 => Ok(Parity::Odd),
32            _ => Err(TaprootError::InvalidParity),
33        }
34    }
35}
36
37impl From<secp256k1::Parity> for Parity {
38    fn from(p: secp256k1::Parity) -> Self {
39        match p {
40            secp256k1::Parity::Even => Parity::Even,
41            secp256k1::Parity::Odd => Parity::Odd,
42        }
43    }
44}
45
46impl From<Parity> for secp256k1::Parity {
47    fn from(p: Parity) -> Self {
48        match p {
49            Parity::Even => secp256k1::Parity::Even,
50            Parity::Odd => secp256k1::Parity::Odd,
51        }
52    }
53}
54
55/// 32-byte x-only public key (BIP340)
56#[derive(Clone, Copy, PartialEq, Eq, Hash)]
57pub struct XOnlyPublicKey {
58    inner: Secp256k1XOnlyPublicKey,
59}
60
61impl XOnlyPublicKey {
62    /// Create from a compressed public key (33 bytes)
63    pub fn from_compressed(data: &[u8; 33]) -> Result<(Self, Parity), TaprootError> {
64        let pubkey = secp256k1::PublicKey::from_slice(data)
65            .map_err(|e| TaprootError::Secp256k1Error(e.to_string()))?;
66        let (xonly, parity) = pubkey.x_only_public_key();
67        Ok((Self { inner: xonly }, parity.into()))
68    }
69
70    /// Create from 32-byte x-only representation
71    pub fn from_slice(data: &[u8]) -> Result<Self, TaprootError> {
72        if data.len() != 32 {
73            return Err(TaprootError::InvalidLength {
74                expected: 32,
75                got: data.len(),
76            });
77        }
78        let inner = Secp256k1XOnlyPublicKey::from_slice(data)
79            .map_err(|e| TaprootError::Secp256k1Error(e.to_string()))?;
80        Ok(Self { inner })
81    }
82
83    /// Create from byte array
84    pub fn from_bytes(data: [u8; 32]) -> Result<Self, TaprootError> {
85        Self::from_slice(&data)
86    }
87
88    /// Serialize to 32 bytes
89    pub fn serialize(&self) -> [u8; 32] {
90        self.inner.serialize()
91    }
92
93    /// Get as byte slice
94    pub fn as_bytes(&self) -> &[u8] {
95        // XOnlyPublicKey doesn't have as_bytes, so we serialize
96        // This is a bit inefficient but safe
97        unsafe {
98            std::slice::from_raw_parts(
99                &self.inner as *const _ as *const u8,
100                32,
101            )
102        }
103    }
104
105    /// Tweak the public key by adding a scalar
106    pub fn tweak_add(
107        &self,
108        secp: &Secp256k1<secp256k1::All>,
109        tweak: &[u8; 32],
110    ) -> Result<(Self, Parity), TaprootError> {
111        let scalar = secp256k1::Scalar::from_be_bytes(*tweak)
112            .map_err(|e| TaprootError::InvalidTweak(e.to_string()))?;
113        let (tweaked, parity) = self.inner.add_tweak(secp, &scalar)
114            .map_err(|e| TaprootError::InvalidTweak(e.to_string()))?;
115        Ok((Self { inner: tweaked }, parity.into()))
116    }
117
118    /// Get the inner secp256k1 x-only public key
119    pub fn inner(&self) -> &Secp256k1XOnlyPublicKey {
120        &self.inner
121    }
122
123    /// Create from secp256k1 x-only public key
124    pub fn from_inner(inner: Secp256k1XOnlyPublicKey) -> Self {
125        Self { inner }
126    }
127
128    /// Convert to full public key with even y-coordinate
129    pub fn to_public_key(&self) -> secp256k1::PublicKey {
130        self.inner.public_key(secp256k1::Parity::Even)
131    }
132}
133
134impl fmt::Debug for XOnlyPublicKey {
135    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
136        write!(f, "XOnlyPublicKey({})", hex::encode(self.serialize()))
137    }
138}
139
140impl fmt::Display for XOnlyPublicKey {
141    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
142        write!(f, "{}", hex::encode(self.serialize()))
143    }
144}
145
146impl std::str::FromStr for XOnlyPublicKey {
147    type Err = TaprootError;
148
149    fn from_str(s: &str) -> Result<Self, Self::Err> {
150        let bytes = hex::decode(s)
151            .map_err(|_| TaprootError::InvalidXOnlyKey)?;
152        Self::from_slice(&bytes)
153    }
154}
155
156
157
158#[cfg(test)]
159mod tests {
160    use super::*;
161
162    #[test]
163    fn test_xonly_from_compressed() {
164        // A valid compressed public key
165        let compressed = [
166            0x02, // even y
167            0x79, 0xbe, 0x66, 0x7e, 0xf9, 0xdc, 0xbb, 0xac,
168            0x55, 0xa0, 0x62, 0x95, 0xce, 0x87, 0x0b, 0x07,
169            0x02, 0x9b, 0xfc, 0xdb, 0x2d, 0xce, 0x28, 0xd9,
170            0x59, 0xf2, 0x81, 0x5b, 0x16, 0xf8, 0x17, 0x98,
171        ];
172
173        let (xonly, parity) = XOnlyPublicKey::from_compressed(&compressed).unwrap();
174        assert_eq!(parity, Parity::Even);
175        assert_eq!(xonly.serialize()[..], compressed[1..]);
176    }
177
178    #[test]
179    fn test_xonly_roundtrip() {
180        let bytes = [
181            0x79, 0xbe, 0x66, 0x7e, 0xf9, 0xdc, 0xbb, 0xac,
182            0x55, 0xa0, 0x62, 0x95, 0xce, 0x87, 0x0b, 0x07,
183            0x02, 0x9b, 0xfc, 0xdb, 0x2d, 0xce, 0x28, 0xd9,
184            0x59, 0xf2, 0x81, 0x5b, 0x16, 0xf8, 0x17, 0x98,
185        ];
186
187        let xonly = XOnlyPublicKey::from_bytes(bytes).unwrap();
188        assert_eq!(xonly.serialize(), bytes);
189    }
190
191    #[test]
192    fn test_xonly_display_parse() {
193        let bytes = [
194            0x79, 0xbe, 0x66, 0x7e, 0xf9, 0xdc, 0xbb, 0xac,
195            0x55, 0xa0, 0x62, 0x95, 0xce, 0x87, 0x0b, 0x07,
196            0x02, 0x9b, 0xfc, 0xdb, 0x2d, 0xce, 0x28, 0xd9,
197            0x59, 0xf2, 0x81, 0x5b, 0x16, 0xf8, 0x17, 0x98,
198        ];
199
200        let xonly = XOnlyPublicKey::from_bytes(bytes).unwrap();
201        let hex_str = xonly.to_string();
202        let parsed: XOnlyPublicKey = hex_str.parse().unwrap();
203        assert_eq!(xonly, parsed);
204    }
205
206    #[test]
207    fn test_parity_conversion() {
208        assert_eq!(Parity::Even.to_u8(), 0);
209        assert_eq!(Parity::Odd.to_u8(), 1);
210        assert_eq!(Parity::from_u8(0).unwrap(), Parity::Even);
211        assert_eq!(Parity::from_u8(1).unwrap(), Parity::Odd);
212        assert!(Parity::from_u8(2).is_err());
213    }
214}