Skip to main content

mina_signer/
pubkey.rs

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