mina_signer/
pubkey.rs

1//! Public key structures and algorithms
2//!
3//! Definition of public key structure and helpers
4
5extern crate alloc;
6use crate::{BaseField, CurvePoint, ScalarField, SecKey};
7use alloc::{string::String, vec, vec::Vec};
8use ark_ec::{short_weierstrass::Affine, AffineRepr, CurveGroup};
9use ark_ff::{BigInteger, PrimeField, Zero};
10use bs58;
11use core::{
12    fmt,
13    ops::{Mul, Neg},
14};
15use o1_utils::FieldHelpers;
16use sha2::{Digest, Sha256};
17use thiserror::Error;
18
19/// Public key errors
20#[derive(Error, Debug, Clone, PartialEq, Eq)]
21pub enum PubKeyError {
22    /// Invalid address length
23    #[error("invalid address length")]
24    AddressLength,
25    /// Invalid address base58
26    #[error("invalid address base58")]
27    AddressBase58,
28    /// Invalid raw address bytes length
29    #[error("invalid raw address bytes length")]
30    AddressRawByteLength,
31    /// Invalid address checksum
32    #[error("invalid address checksum")]
33    AddressChecksum,
34    /// Invalid address version
35    #[error("invalid address version")]
36    AddressVersion,
37    /// Invalid x-coordinate bytes
38    #[error("invalid x-coordinate bytes")]
39    XCoordinateBytes,
40    /// Invalid x-coordinate
41    #[error("invalid x-coordinate")]
42    XCoordinate,
43    /// Point not on curve
44    #[error("point not on curve")]
45    YCoordinateBytes,
46    /// Invalid y-coordinate
47    #[error("invalid y-coordinate bytes")]
48    YCoordinateParityBytes,
49    /// Invalid y-coordinate parity
50    #[error("invalid y-coordinate parity bytes")]
51    YCoordinateParity,
52    /// Invalid y-coordinate parity
53    #[error("invalid y-coordinate parity")]
54    NonCurvePoint,
55    /// Invalid hex
56    #[error("invalid public key hex")]
57    Hex,
58    /// Invalid secret key
59    #[error("invalid secret key")]
60    SecKey,
61}
62/// Public key Result
63pub type Result<T> = core::result::Result<T, PubKeyError>;
64
65/// Length of Mina addresses
66pub const MINA_ADDRESS_LEN: usize = 55;
67const MINA_ADDRESS_RAW_LEN: usize = 40;
68
69/// Public key
70#[derive(Clone, Debug, PartialEq, Eq)]
71pub struct PubKey(CurvePoint);
72
73impl PubKey {
74    /// Create public key from curve point
75    /// Note: Does not check point is on curve
76    pub fn from_point_unsafe(point: CurvePoint) -> Self {
77        Self(point)
78    }
79
80    /// Deserialize public key from bytes
81    /// # Errors
82    ///
83    /// Will give error if `bytes` do not match certain requirements.
84    pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
85        if bytes.len() != BaseField::size_in_bytes() * 2 {
86            return Err(PubKeyError::YCoordinateBytes);
87        }
88        let x = BaseField::from_bytes(&bytes[0..BaseField::size_in_bytes()])
89            .map_err(|_| PubKeyError::XCoordinateBytes)?;
90        let y = BaseField::from_bytes(&bytes[BaseField::size_in_bytes()..])
91            .map_err(|_| PubKeyError::YCoordinateBytes)?;
92        let pt = CurvePoint::get_point_from_x_unchecked(x, y.0.is_odd())
93            .ok_or(PubKeyError::XCoordinate)?;
94        if pt.y != y {
95            return Err(PubKeyError::NonCurvePoint);
96        }
97
98        let public = Affine {
99            x,
100            y,
101            infinity: pt.infinity,
102        };
103        if !public.is_on_curve() {
104            return Err(PubKeyError::NonCurvePoint);
105        }
106
107        // Safe now because we checked point is on the curve
108        Ok(PubKey::from_point_unsafe(public))
109    }
110
111    /// Deserialize public key from hex
112    ///
113    /// # Errors
114    ///
115    /// Will give error if `hex` string does not match certain requirements.
116    pub fn from_hex(public_hex: &str) -> Result<Self> {
117        let bytes: Vec<u8> = hex::decode(public_hex).map_err(|_| PubKeyError::Hex)?;
118        PubKey::from_bytes(&bytes)
119    }
120
121    /// Create public key from a secret key
122    pub fn from_secret_key(secret_key: SecKey) -> Result<Self> {
123        if secret_key.clone().into_scalar() == ScalarField::zero() {
124            return Err(PubKeyError::SecKey);
125        }
126        let pt = CurvePoint::generator()
127            .mul(secret_key.into_scalar())
128            .into_affine();
129        if !pt.is_on_curve() {
130            return Err(PubKeyError::NonCurvePoint);
131        }
132        Ok(PubKey::from_point_unsafe(pt))
133    }
134
135    /// Deserialize Mina address into public key
136    ///
137    /// # Errors
138    ///
139    /// Will give error if `address` string does not match certain requirements.
140    pub fn from_address(address: &str) -> Result<Self> {
141        if address.len() != MINA_ADDRESS_LEN {
142            return Err(PubKeyError::AddressLength);
143        }
144
145        let bytes = bs58::decode(address)
146            .into_vec()
147            .map_err(|_| PubKeyError::AddressBase58)?;
148
149        if bytes.len() != MINA_ADDRESS_RAW_LEN {
150            return Err(PubKeyError::AddressRawByteLength);
151        }
152
153        let (raw, checksum) = (&bytes[..bytes.len() - 4], &bytes[bytes.len() - 4..]);
154        let hash = Sha256::digest(&Sha256::digest(raw)[..]);
155        if checksum != &hash[..4] {
156            return Err(PubKeyError::AddressChecksum);
157        }
158
159        let (version, x_bytes, y_parity) = (
160            &raw[..3],
161            &raw[3..bytes.len() - 5],
162            raw[bytes.len() - 5] == 0x01,
163        );
164        if version != [0xcb, 0x01, 0x01] {
165            return Err(PubKeyError::AddressVersion);
166        }
167
168        let x = BaseField::from_bytes(x_bytes).map_err(|_| PubKeyError::XCoordinateBytes)?;
169        let mut pt =
170            CurvePoint::get_point_from_x_unchecked(x, y_parity).ok_or(PubKeyError::XCoordinate)?;
171
172        if pt.y.into_bigint().is_even() == y_parity {
173            pt.y = pt.y.neg();
174        }
175
176        if !pt.is_on_curve() {
177            return Err(PubKeyError::NonCurvePoint);
178        }
179
180        // Safe now because we checked point pt is on curve
181        Ok(PubKey::from_point_unsafe(pt))
182    }
183
184    /// Borrow public key as curve point
185    pub fn point(&self) -> &CurvePoint {
186        &self.0
187    }
188
189    /// Convert public key into curve point
190    pub fn into_point(self) -> CurvePoint {
191        self.0
192    }
193
194    /// Convert public key into compressed public key
195    pub fn into_compressed(&self) -> CompressedPubKey {
196        let point = self.0;
197        CompressedPubKey {
198            x: point.x,
199            is_odd: point.y.into_bigint().is_odd(),
200        }
201    }
202
203    /// Serialize public key into corresponding Mina address
204    pub fn into_address(&self) -> String {
205        let point = self.point();
206        into_address(&point.x, point.y.into_bigint().is_odd())
207    }
208
209    /// Deserialize public key into bytes
210    pub fn to_bytes(&self) -> Vec<u8> {
211        let point = self.point();
212        [point.x.to_bytes(), point.y.to_bytes()].concat()
213    }
214
215    /// Deserialize public key into hex
216    pub fn to_hex(&self) -> String {
217        let point = self.point();
218        point.x.to_hex() + point.y.to_hex().as_str()
219    }
220}
221
222impl fmt::Display for PubKey {
223    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
224        write!(f, "{}", self.to_hex())
225    }
226}
227
228/// Compressed public keys consist of x-coordinate and y-coordinate parity.
229#[derive(Clone, Debug, PartialEq, Eq)]
230pub struct CompressedPubKey {
231    /// X-coordinate
232    pub x: BaseField,
233
234    /// Parity of y-coordinate
235    pub is_odd: bool,
236}
237
238fn into_address(x: &BaseField, is_odd: bool) -> String {
239    let mut raw: Vec<u8> = vec![
240        0xcb, // version for base58 check
241        0x01, // non_zero_curve_point version
242        0x01, // compressed_poly version
243    ];
244
245    // pub key x-coordinate
246    raw.extend(x.to_bytes());
247
248    // pub key y-coordinate parity
249    raw.push(u8::from(is_odd));
250
251    // 4-byte checksum
252    let hash = Sha256::digest(&Sha256::digest(&raw[..])[..]);
253    raw.extend(&hash[..4]);
254
255    // The raw buffer is MINA_ADDRESS_RAW_LEN (= 40) bytes in length
256    bs58::encode(raw).into_string()
257}
258
259impl CompressedPubKey {
260    /// Serialize compressed public key into corresponding Mina address
261    pub fn into_address(&self) -> String {
262        into_address(&self.x, self.is_odd)
263    }
264
265    /// Deserialize compressed public key from bytes
266    /// # Errors
267    ///
268    /// Will give error if `bytes` do not match certain requirements.
269    pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
270        let x = BaseField::from_bytes(&bytes[0..BaseField::size_in_bytes()])
271            .map_err(|_| PubKeyError::XCoordinateBytes)?;
272        let parity_bytes = &bytes[BaseField::size_in_bytes()..];
273        if parity_bytes.len() != 1 {
274            return Err(PubKeyError::YCoordinateParityBytes);
275        }
276        let is_odd = if parity_bytes[0] == 0x01 {
277            true // Odd
278        } else if parity_bytes[0] == 0x00 {
279            false // Even
280        } else {
281            return Err(PubKeyError::YCoordinateParity);
282        };
283        let public =
284            CurvePoint::get_point_from_x_unchecked(x, is_odd).ok_or(PubKeyError::XCoordinate)?;
285        if !public.is_on_curve() {
286            return Err(PubKeyError::NonCurvePoint);
287        }
288
289        // Safe now because we checked point is on the curve
290        Ok(Self { x, is_odd })
291    }
292
293    /// Deserialize compressed public key from hex
294    ///
295    /// # Errors
296    ///
297    /// Will give error if `hex` string does not match certain requirements.
298    pub fn from_hex(public_hex: &str) -> Result<Self> {
299        let bytes: Vec<u8> = hex::decode(public_hex).map_err(|_| PubKeyError::Hex)?;
300        Self::from_bytes(&bytes)
301    }
302
303    /// Create compressed public key from a secret key
304    pub fn from_secret_key(sec_key: SecKey) -> Self {
305        // We do not need to check point is on the curve, since it's derived directly from the generator point
306        let public = PubKey::from_point_unsafe(
307            CurvePoint::generator()
308                .mul(sec_key.into_scalar())
309                .into_affine(),
310        );
311        public.into_compressed()
312    }
313
314    /// Deserialize Mina address into compressed public key (via an uncompressed `PubKey`)
315    ///
316    /// # Errors
317    ///
318    /// Will give error if `PubKey::from_address()` returns error.
319    pub fn from_address(address: &str) -> Result<Self> {
320        Ok(PubKey::from_address(address)?.into_compressed())
321    }
322
323    /// The empty [`CompressedPubKey`] value that is used as `public_key` in empty account
324    /// and [None] value for calculating the hash of [`Option<CompressedPubKey>`], etc.
325    pub fn empty() -> Self {
326        Self {
327            x: BaseField::zero(),
328            is_odd: false,
329        }
330    }
331
332    /// Deserialize compressed public key into bytes
333    pub fn to_bytes(&self) -> Vec<u8> {
334        let x_bytes = self.x.to_bytes();
335        let is_odd_bytes = vec![if self.is_odd { 0x01u8 } else { 0x00u8 }];
336        [x_bytes, is_odd_bytes].concat()
337    }
338
339    /// Deserialize compressed public key into hex
340    pub fn to_hex(&self) -> String {
341        hex::encode(self.to_bytes())
342    }
343}