1extern 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#[derive(Error, Debug, Clone, PartialEq, Eq)]
22pub enum PubKeyError {
23 #[error("invalid address length")]
25 AddressLength,
26 #[error("invalid address base58")]
28 AddressBase58,
29 #[error("invalid raw address bytes length")]
31 AddressRawByteLength,
32 #[error("invalid address checksum")]
34 AddressChecksum,
35 #[error("invalid address version")]
37 AddressVersion,
38 #[error("invalid x-coordinate bytes")]
40 XCoordinateBytes,
41 #[error("invalid x-coordinate")]
43 XCoordinate,
44 #[error("point not on curve")]
46 YCoordinateBytes,
47 #[error("invalid y-coordinate bytes")]
49 YCoordinateParityBytes,
50 #[error("invalid y-coordinate parity bytes")]
52 YCoordinateParity,
53 #[error("invalid y-coordinate parity")]
55 NonCurvePoint,
56 #[error("invalid public key hex")]
58 Hex,
59 #[error("invalid secret key")]
61 SecKey,
62}
63pub type Result<T> = core::result::Result<T, PubKeyError>;
65
66pub const MINA_ADDRESS_LEN: usize = 55;
68const MINA_ADDRESS_RAW_LEN: usize = 40;
69
70#[derive(Clone, Debug, PartialEq, Eq)]
72pub struct PubKey(CurvePoint);
73
74impl PubKey {
75 #[allow(clippy::needless_pass_by_value)]
78 #[must_use]
79 pub const fn from_point_unsafe(point: CurvePoint) -> Self {
80 Self(point)
81 }
82
83 pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
88 if bytes.len() != BaseField::size_in_bytes() * 2 {
89 return Err(PubKeyError::YCoordinateBytes);
90 }
91 let x = BaseField::from_bytes(&bytes[0..BaseField::size_in_bytes()])
92 .map_err(|_| PubKeyError::XCoordinateBytes)?;
93 let y = BaseField::from_bytes(&bytes[BaseField::size_in_bytes()..])
94 .map_err(|_| PubKeyError::YCoordinateBytes)?;
95 let pt = CurvePoint::get_point_from_x_unchecked(x, y.0.is_odd())
96 .ok_or(PubKeyError::XCoordinate)?;
97 if pt.y != y {
98 return Err(PubKeyError::NonCurvePoint);
99 }
100
101 let public = Affine {
102 x,
103 y,
104 infinity: pt.infinity,
105 };
106 if !public.is_on_curve() {
107 return Err(PubKeyError::NonCurvePoint);
108 }
109
110 Ok(Self::from_point_unsafe(public))
112 }
113
114 pub fn from_hex(public_hex: &str) -> Result<Self> {
120 let bytes: Vec<u8> = hex::decode(public_hex).map_err(|_| PubKeyError::Hex)?;
121 Self::from_bytes(&bytes)
122 }
123
124 pub fn from_secret_key(secret_key: &SecKey) -> Result<Self> {
131 if *secret_key.scalar() == ScalarField::zero() {
132 return Err(PubKeyError::SecKey);
133 }
134 let pt = CurvePoint::generator()
135 .mul(*secret_key.scalar())
136 .into_affine();
137 if !pt.is_on_curve() {
138 return Err(PubKeyError::NonCurvePoint);
139 }
140 Ok(Self::from_point_unsafe(pt))
141 }
142
143 pub fn from_address(address: &str) -> Result<Self> {
149 if address.len() != MINA_ADDRESS_LEN {
150 return Err(PubKeyError::AddressLength);
151 }
152
153 let bytes = bs58::decode(address)
154 .into_vec()
155 .map_err(|_| PubKeyError::AddressBase58)?;
156
157 if bytes.len() != MINA_ADDRESS_RAW_LEN {
158 return Err(PubKeyError::AddressRawByteLength);
159 }
160
161 let (raw, checksum) = (&bytes[..bytes.len() - 4], &bytes[bytes.len() - 4..]);
162 let hash = Sha256::digest(&Sha256::digest(raw)[..]);
163 if checksum != &hash[..4] {
164 return Err(PubKeyError::AddressChecksum);
165 }
166
167 let (version, x_bytes, y_parity) = (
168 &raw[..3],
169 &raw[3..bytes.len() - 5],
170 raw[bytes.len() - 5] == 0x01,
171 );
172 if version != [0xcb, 0x01, 0x01] {
173 return Err(PubKeyError::AddressVersion);
174 }
175
176 let x = BaseField::from_bytes(x_bytes).map_err(|_| PubKeyError::XCoordinateBytes)?;
177 let mut pt =
178 CurvePoint::get_point_from_x_unchecked(x, y_parity).ok_or(PubKeyError::XCoordinate)?;
179
180 if pt.y.into_bigint().is_even() == y_parity {
181 pt.y = pt.y.neg();
182 }
183
184 if !pt.is_on_curve() {
185 return Err(PubKeyError::NonCurvePoint);
186 }
187
188 Ok(Self::from_point_unsafe(pt))
190 }
191
192 #[must_use = "borrowed curve point must be used"]
194 pub const fn point(&self) -> &CurvePoint {
195 &self.0
196 }
197
198 pub const fn into_point(self) -> CurvePoint {
200 self.0
201 }
202
203 #[must_use]
205 pub fn into_compressed(&self) -> CompressedPubKey {
206 let point = self.0;
207 CompressedPubKey {
208 x: point.x,
209 is_odd: point.y.into_bigint().is_odd(),
210 }
211 }
212
213 #[must_use]
215 pub fn into_address(&self) -> String {
216 let point = self.point();
217 into_address(&point.x, point.y.into_bigint().is_odd())
218 }
219
220 #[must_use]
226 pub fn to_bytes(&self) -> Vec<u8> {
227 let point = self.point();
228 [point.x.to_bytes(), point.y.to_bytes()].concat()
229 }
230
231 #[must_use]
237 pub fn to_hex(&self) -> String {
238 let point = self.point();
239 point.x.to_hex() + point.y.to_hex().as_str()
240 }
241}
242
243impl fmt::Display for PubKey {
244 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
245 write!(f, "{}", self.to_hex())
246 }
247}
248
249#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
251pub struct CompressedPubKey {
252 pub x: BaseField,
254
255 pub is_odd: bool,
257}
258
259fn into_address(x: &BaseField, is_odd: bool) -> String {
260 let mut raw: Vec<u8> = vec![
261 0xcb, 0x01, 0x01, ];
265
266 raw.extend(x.to_bytes());
268
269 raw.push(u8::from(is_odd));
271
272 let hash = Sha256::digest(&Sha256::digest(&raw[..])[..]);
274 raw.extend(&hash[..4]);
275
276 bs58::encode(raw).into_string()
278}
279
280impl CompressedPubKey {
281 #[must_use]
283 pub fn into_address(&self) -> String {
284 into_address(&self.x, self.is_odd)
285 }
286
287 pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
292 let x = BaseField::from_bytes(&bytes[0..BaseField::size_in_bytes()])
293 .map_err(|_| PubKeyError::XCoordinateBytes)?;
294 let parity_bytes = &bytes[BaseField::size_in_bytes()..];
295 if parity_bytes.len() != 1 {
296 return Err(PubKeyError::YCoordinateParityBytes);
297 }
298 let is_odd = if parity_bytes[0] == 0x01 {
299 true } else if parity_bytes[0] == 0x00 {
301 false } else {
303 return Err(PubKeyError::YCoordinateParity);
304 };
305 let public =
306 CurvePoint::get_point_from_x_unchecked(x, is_odd).ok_or(PubKeyError::XCoordinate)?;
307 if !public.is_on_curve() {
308 return Err(PubKeyError::NonCurvePoint);
309 }
310
311 Ok(Self { x, is_odd })
313 }
314
315 pub fn from_hex(public_hex: &str) -> Result<Self> {
321 let bytes: Vec<u8> = hex::decode(public_hex).map_err(|_| PubKeyError::Hex)?;
322 Self::from_bytes(&bytes)
323 }
324
325 #[must_use]
327 pub fn from_secret_key(sec_key: SecKey) -> Self {
328 let public = PubKey::from_point_unsafe(
331 CurvePoint::generator()
332 .mul(sec_key.into_scalar())
333 .into_affine(),
334 );
335 public.into_compressed()
336 }
337
338 pub fn from_address(address: &str) -> Result<Self> {
345 Ok(PubKey::from_address(address)?.into_compressed())
346 }
347
348 #[must_use]
352 pub fn empty() -> Self {
353 Self {
354 x: BaseField::zero(),
355 is_odd: false,
356 }
357 }
358
359 #[must_use]
361 pub fn to_bytes(&self) -> Vec<u8> {
362 let x_bytes = self.x.to_bytes();
363 let is_odd_bytes = vec![u8::from(self.is_odd)];
364 [x_bytes, is_odd_bytes].concat()
365 }
366
367 #[must_use]
369 pub fn to_hex(&self) -> String {
370 hex::encode(self.to_bytes())
371 }
372}