|
@@ -0,0 +1,164 @@
|
|
|
+use bech32::{self, FromBase32, ToBase32, Variant};
|
|
|
+use serde::{
|
|
|
+ de::{self, Deserialize, Deserializer},
|
|
|
+ ser::{self, Serializer},
|
|
|
+};
|
|
|
+use thiserror::Error;
|
|
|
+
|
|
|
+#[derive(Error, Debug)]
|
|
|
+pub enum Error {
|
|
|
+ #[error("Unexpected HRP (Human readable part)")]
|
|
|
+ UnexpectedHrp,
|
|
|
+
|
|
|
+ #[error("Error decoding: {0}")]
|
|
|
+ DecodingError(#[from] hex::FromHexError),
|
|
|
+
|
|
|
+ #[error("Data is too long ({0} bytes). Addresses are up to 32 bytes")]
|
|
|
+ TooLong(usize),
|
|
|
+
|
|
|
+ #[error("Bech32 error: {0}")]
|
|
|
+ Bech32(#[from] bech32::Error),
|
|
|
+}
|
|
|
+
|
|
|
+#[derive(Debug, Clone, PartialEq, Eq, Copy)]
|
|
|
+pub enum Type {
|
|
|
+ NPub,
|
|
|
+ NSec,
|
|
|
+ Note,
|
|
|
+ Unknown,
|
|
|
+}
|
|
|
+
|
|
|
+impl ToString for Type {
|
|
|
+ fn to_string(&self) -> String {
|
|
|
+ match *self {
|
|
|
+ Self::NPub => "npub".to_owned(),
|
|
|
+ Self::NSec => "nsec".to_owned(),
|
|
|
+ Self::Note => "note".to_owned(),
|
|
|
+ Self::Unknown => "".to_owned(),
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+#[derive(Debug, Clone, PartialEq, Eq)]
|
|
|
+pub struct Addr {
|
|
|
+ bytes: Vec<u8>,
|
|
|
+ pub typ: Type,
|
|
|
+}
|
|
|
+
|
|
|
+impl Addr {
|
|
|
+ pub fn try_from_public_key_str(typ: Type, pk: &str) -> Result<Self, Error> {
|
|
|
+ Addr::try_from_bytes(typ, hex::decode(pk)?)
|
|
|
+ }
|
|
|
+
|
|
|
+ pub fn try_from_bytes(typ: Type, bytes: Vec<u8>) -> Result<Self, Error> {
|
|
|
+ if bytes.len() < 32 {
|
|
|
+ return Err(Error::TooLong(bytes.len()));
|
|
|
+ }
|
|
|
+
|
|
|
+ Ok(Addr { bytes, typ })
|
|
|
+ }
|
|
|
+
|
|
|
+ pub fn change_type(self, new_type: Type) -> Self {
|
|
|
+ Self {
|
|
|
+ bytes: self.bytes,
|
|
|
+ typ: new_type,
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ pub fn to_public_key(&self) -> String {
|
|
|
+ hex::encode(&self.bytes)
|
|
|
+ }
|
|
|
+
|
|
|
+ pub fn try_to_human(&self) -> Result<String, Error> {
|
|
|
+ Ok(bech32::encode(
|
|
|
+ &self.typ.to_string(),
|
|
|
+ self.bytes.to_base32(),
|
|
|
+ Variant::Bech32,
|
|
|
+ )?)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+impl TryFrom<&str> for Addr {
|
|
|
+ type Error = Error;
|
|
|
+
|
|
|
+ fn try_from(value: &str) -> Result<Self, Self::Error> {
|
|
|
+ if let Ok(bytes) = hex::decode(value) {
|
|
|
+ return Ok(Self {
|
|
|
+ bytes,
|
|
|
+ typ: Type::Unknown,
|
|
|
+ });
|
|
|
+ }
|
|
|
+ let (hrp, bytes, _) = bech32::decode(value)?;
|
|
|
+ let typ = match hrp.to_lowercase().as_str() {
|
|
|
+ "npub" => Ok(Type::NPub),
|
|
|
+ "nsec" => Ok(Type::NSec),
|
|
|
+ "note" => Ok(Type::Note),
|
|
|
+ _ => Err(Error::UnexpectedHrp),
|
|
|
+ }?;
|
|
|
+
|
|
|
+ Ok(Self {
|
|
|
+ bytes: Vec::<u8>::from_base32(&bytes)?,
|
|
|
+ typ,
|
|
|
+ })
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+impl<'de> Deserialize<'de> for Addr {
|
|
|
+ fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
|
|
+ where
|
|
|
+ D: Deserializer<'de>,
|
|
|
+ {
|
|
|
+ let s = <&str>::deserialize(deserializer)?;
|
|
|
+ let t = s
|
|
|
+ .try_into()
|
|
|
+ .map_err(|e: Error| de::Error::custom(e.to_string()))?;
|
|
|
+ Ok(t)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+impl ser::Serialize for Addr {
|
|
|
+ fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
|
|
+ where
|
|
|
+ S: Serializer,
|
|
|
+ {
|
|
|
+ let str = self.to_public_key();
|
|
|
+ serializer.serialize_str(&str)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+#[cfg(test)]
|
|
|
+mod test {
|
|
|
+ use super::*;
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn deserializes_json() {
|
|
|
+ let json_str = "\"b2815682cfc83fcd2c3add05785cf4573dd388457069974cc6d8cca06b3c3b78\"";
|
|
|
+ let x: Addr = serde_json::from_str(json_str).expect("valid pk");
|
|
|
+ assert_eq!(Type::Unknown, x.typ);
|
|
|
+ assert_eq!(
|
|
|
+ "npub1k2q4dqk0eqlu6tp6m5zhsh852u7a8zz9wp5ewnxxmrx2q6eu8duq3ydzzr",
|
|
|
+ x.change_type(Type::NPub)
|
|
|
+ .try_to_human()
|
|
|
+ .expect("valid addr"),
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn deserializes_from_public_key_and_json() {
|
|
|
+ let addr1 = Addr::try_from_public_key_str(
|
|
|
+ Type::NPub,
|
|
|
+ "b2815682cfc83fcd2c3add05785cf4573dd388457069974cc6d8cca06b3c3b78",
|
|
|
+ )
|
|
|
+ .expect("valid addr");
|
|
|
+ assert_eq!(
|
|
|
+ "npub1k2q4dqk0eqlu6tp6m5zhsh852u7a8zz9wp5ewnxxmrx2q6eu8duq3ydzzr".to_owned(),
|
|
|
+ addr1.try_to_human().expect("valid address")
|
|
|
+ );
|
|
|
+
|
|
|
+ let addr2: Addr = serde_json::from_str(
|
|
|
+ "\"npub1k2q4dqk0eqlu6tp6m5zhsh852u7a8zz9wp5ewnxxmrx2q6eu8duq3ydzzr\"",
|
|
|
+ )
|
|
|
+ .expect("valid addr");
|
|
|
+ assert_eq!(addr1, addr2);
|
|
|
+ }
|
|
|
+}
|