use super::COMPRESSED_PUBKEY_LEN;
use crate::base58;
use crate::error::{Error, Result};
use crate::types::{write_raw, GrapheneSerialize};
use std::fmt;
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub struct PublicKey {
inner: secp256k1::PublicKey,
}
impl PublicKey {
pub(crate) fn from_inner(inner: secp256k1::PublicKey) -> Self {
PublicKey { inner }
}
pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
let inner = secp256k1::PublicKey::from_slice(bytes)
.map_err(|e| Error::key(format!("not a valid secp256k1 point: {e}")))?;
Ok(PublicKey { inner })
}
pub fn from_hex(s: &str) -> Result<Self> {
let bytes = crate::hex::decode_vec(s.trim()).map_err(|e| match e {
crate::hex::HexError::Length => {
Error::key("public key hex has an odd number of characters")
}
crate::hex::HexError::NotHex => Error::key("public key is not valid hex"),
})?;
Self::from_bytes(&bytes)
}
pub fn from_prefixed(s: &str, prefix: &str) -> Result<Self> {
let s = s.trim();
let rest = s.strip_prefix(prefix).ok_or_else(|| {
Error::key(format!(
"public key does not start with the expected prefix {prefix:?}"
))
})?;
let payload = base58::decode_gph_check(rest)?;
if payload.len() != COMPRESSED_PUBKEY_LEN {
return Err(Error::key(format!(
"public key payload must be {COMPRESSED_PUBKEY_LEN} bytes, got {}",
payload.len()
)));
}
Self::from_bytes(&payload)
}
pub fn from_prefixed_any(s: &str) -> Result<Self> {
for prefix in ["STM", "TST", "STX"] {
if s.trim().starts_with(prefix) {
return Self::from_prefixed(s, prefix);
}
}
Err(Error::key("public key has no recognised chain prefix"))
}
pub fn to_bytes(&self) -> [u8; COMPRESSED_PUBKEY_LEN] {
self.inner.serialize()
}
pub fn to_uncompressed_bytes(&self) -> [u8; 65] {
self.inner.serialize_uncompressed()
}
pub fn to_hex(&self) -> String {
self.to_bytes().iter().map(|b| format!("{b:02x}")).collect()
}
#[cfg(feature = "memo")]
pub(crate) fn inner_ref(&self) -> &secp256k1::PublicKey {
&self.inner
}
pub fn to_prefixed(&self, prefix: &str) -> String {
format!("{prefix}{}", base58::encode_gph_check(&self.to_bytes()))
}
}
impl GrapheneSerialize for PublicKey {
fn append_to(&self, out: &mut Vec<u8>) -> Result<()> {
write_raw(out, &self.to_bytes());
Ok(())
}
}
impl fmt::Debug for PublicKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "PublicKey({})", self.to_prefixed("STM"))
}
}
impl fmt::Display for PublicKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.to_prefixed("STM"))
}
}
impl PartialOrd for PublicKey {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for PublicKey {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.to_bytes().cmp(&other.to_bytes())
}
}
pub const NULL_PUBLIC_KEY: &str = "STM1111111111111111111111111111111114T1Anm";
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum MaybePublicKey {
Null,
Key(PublicKey),
}
impl MaybePublicKey {
pub fn key(&self) -> Option<&PublicKey> {
match self {
MaybePublicKey::Null => None,
MaybePublicKey::Key(k) => Some(k),
}
}
pub fn is_null(&self) -> bool {
matches!(self, MaybePublicKey::Null)
}
pub fn from_prefixed_any(s: &str) -> Result<Self> {
let s = s.trim();
for prefix in ["STM", "TST", "STX"] {
if let Some(rest) = s.strip_prefix(prefix) {
let payload = base58::decode_gph_check(rest)?;
if payload == [0u8; COMPRESSED_PUBKEY_LEN] {
return Ok(MaybePublicKey::Null);
}
return PublicKey::from_bytes(&payload).map(MaybePublicKey::Key);
}
}
Err(Error::key("public key has no recognised chain prefix"))
}
pub fn to_prefixed(&self, prefix: &str) -> String {
match self {
MaybePublicKey::Null => {
format!(
"{prefix}{}",
base58::encode_gph_check(&[0u8; COMPRESSED_PUBKEY_LEN])
)
}
MaybePublicKey::Key(k) => k.to_prefixed(prefix),
}
}
}
impl fmt::Display for MaybePublicKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.to_prefixed("STM"))
}
}
impl serde::Serialize for MaybePublicKey {
fn serialize<S: serde::Serializer>(&self, s: S) -> std::result::Result<S::Ok, S::Error> {
s.serialize_str(&self.to_prefixed("STM"))
}
}
impl<'de> serde::Deserialize<'de> for MaybePublicKey {
fn deserialize<D: serde::Deserializer<'de>>(d: D) -> std::result::Result<Self, D::Error> {
use serde::de::Error as _;
let s = String::deserialize(d)?;
MaybePublicKey::from_prefixed_any(&s).map_err(D::Error::custom)
}
}
impl crate::reader::GrapheneDeserialize for PublicKey {
fn read_from(r: &mut crate::reader::Reader<'_>) -> Result<Self> {
let bytes = r.raw(COMPRESSED_PUBKEY_LEN)?;
Self::from_bytes(&bytes)
}
}
impl<'de> serde::Deserialize<'de> for PublicKey {
fn deserialize<D: serde::Deserializer<'de>>(d: D) -> std::result::Result<Self, D::Error> {
use serde::de::Error as _;
let s = String::deserialize(d)?;
PublicKey::from_prefixed_any(&s).map_err(D::Error::custom)
}
}
impl serde::Serialize for PublicKey {
fn serialize<S: serde::Serializer>(&self, s: S) -> std::result::Result<S::Ok, S::Error> {
s.serialize_str(&self.to_prefixed("STM"))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::keys::PrivateKey;
const TEST_WIF: &str = "5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3";
#[test]
fn derives_the_known_public_key() {
let priv_key = PrivateKey::from_wif(TEST_WIF).unwrap();
let pubkey = priv_key.public_key();
let text = pubkey.to_prefixed("STM");
assert!(text.starts_with("STM"));
assert_eq!(pubkey.to_bytes().len(), 33);
assert_eq!(PublicKey::from_prefixed(&text, "STM").unwrap(), pubkey);
}
#[test]
fn prefix_must_match() {
let pubkey = PrivateKey::from_wif(TEST_WIF).unwrap().public_key();
let stm = pubkey.to_prefixed("STM");
assert!(PublicKey::from_prefixed(&stm, "TST").is_err());
assert!(PublicKey::from_prefixed(&stm, "STM").is_ok());
}
#[test]
fn rejects_off_curve_points() {
let mut bad = [0u8; 33];
bad[32] = 0x05;
for sign_byte in [0x02u8, 0x03] {
bad[0] = sign_byte;
assert!(PublicKey::from_bytes(&bad).is_err());
}
let mut zero_x = [0u8; 33];
zero_x[0] = 0x02;
assert!(PublicKey::from_bytes(&zero_x).is_err());
}
#[test]
fn rejects_corrupted_checksum() {
let pubkey = PrivateKey::from_wif(TEST_WIF).unwrap().public_key();
let mut text: Vec<char> = pubkey.to_prefixed("STM").chars().collect();
let last = text.len() - 1;
text[last] = if text[last] == 'a' { 'b' } else { 'a' };
let text: String = text.into_iter().collect();
assert!(PublicKey::from_prefixed(&text, "STM").is_err());
}
#[test]
fn serializes_as_a_bare_33_byte_array() {
let pubkey = PrivateKey::from_wif(TEST_WIF).unwrap().public_key();
let wire = pubkey.to_wire().unwrap();
assert_eq!(wire.len(), 33, "no varint length prefix on a fixed array");
assert_eq!(wire, pubkey.to_bytes().to_vec());
}
#[test]
fn the_null_key_is_not_a_public_key_but_is_a_maybe_key() {
assert!(PublicKey::from_prefixed_any(NULL_PUBLIC_KEY).is_err());
let maybe = MaybePublicKey::from_prefixed_any(NULL_PUBLIC_KEY).unwrap();
assert!(maybe.is_null());
assert!(maybe.key().is_none());
assert_eq!(maybe.to_prefixed("STM"), NULL_PUBLIC_KEY);
}
#[test]
fn maybe_key_round_trips_a_real_key() {
let real = PrivateKey::from_wif(TEST_WIF).unwrap().public_key();
let text = real.to_prefixed("STM");
let maybe = MaybePublicKey::from_prefixed_any(&text).unwrap();
assert_eq!(maybe.key(), Some(&real));
assert_eq!(maybe.to_prefixed("STM"), text);
assert_eq!(
serde_json::from_str::<MaybePublicKey>(&format!("\"{text}\"")).unwrap(),
maybe
);
}
#[test]
fn ordering_is_by_serialized_key_not_by_address() {
let a = PrivateKey::generate().public_key();
let b = PrivateKey::generate().public_key();
let mut v = [a, b];
v.sort();
assert!(v[0].to_bytes() <= v[1].to_bytes());
}
}