#![allow(missing_docs)]
use alloc::string::{FromUtf8Error, String, ToString};
use alloc::vec::Vec;
use core::fmt;
use core::str::FromStr;
use bitcoin::bech32::{self, FromBase32, ToBase32, Variant};
use bitcoin::hashes::Hash;
use super::nip01::Coordinate;
#[cfg(feature = "nip49")]
use super::nip49::{self, EncryptedSecretKey};
use crate::event::id::{self, EventId};
use crate::types::url::{self, TryIntoUrl, Url};
use crate::{key, Kind, PublicKey, SecretKey};
pub const PREFIX_BECH32_SECRET_KEY: &str = "nsec";
pub const PREFIX_BECH32_SECRET_KEY_ENCRYPTED: &str = "ncryptsec";
pub const PREFIX_BECH32_PUBLIC_KEY: &str = "npub";
pub const PREFIX_BECH32_NOTE_ID: &str = "note";
pub const PREFIX_BECH32_PROFILE: &str = "nprofile";
pub const PREFIX_BECH32_EVENT: &str = "nevent";
pub const PREFIX_BECH32_PARAMETERIZED_REPLACEABLE_EVENT: &str = "naddr";
pub const SPECIAL: u8 = 0;
pub const RELAY: u8 = 1;
pub const AUTHOR: u8 = 2;
pub const KIND: u8 = 3;
#[derive(Debug, Eq, PartialEq)]
pub enum Error {
Url(url::ParseError),
Bech32(bech32::Error),
UTF8(FromUtf8Error),
Hash(bitcoin::hashes::Error),
Keys(key::Error),
EventId(id::Error),
#[cfg(feature = "nip49")]
NIP49(nip49::Error),
WrongPrefixOrVariant,
NotImplemented,
FieldMissing(String),
TLV,
TryFromSlice,
}
#[cfg(feature = "std")]
impl std::error::Error for Error {}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Url(e) => write!(f, "Url: {e}"),
Self::Bech32(e) => write!(f, "Bech32: {e}"),
Self::UTF8(e) => write!(f, "UTF8: {e}"),
Self::Hash(e) => write!(f, "Hash: {e}"),
Self::Keys(e) => write!(f, "Keys: {e}"),
Self::EventId(e) => write!(f, "Event ID: {e}"),
#[cfg(feature = "nip49")]
Self::NIP49(e) => write!(f, "{e}"),
Self::WrongPrefixOrVariant => write!(f, "Wrong prefix or variant"),
Self::FieldMissing(name) => write!(f, "Field missing: {name}"),
Self::TLV => write!(f, "TLV (type-length-value) error"),
Self::TryFromSlice => write!(f, "Impossible to perform conversion from slice"),
Self::NotImplemented => write!(f, "Not implemented"),
}
}
}
impl From<url::ParseError> for Error {
fn from(e: url::ParseError) -> Self {
Self::Url(e)
}
}
impl From<bech32::Error> for Error {
fn from(e: bech32::Error) -> Self {
Self::Bech32(e)
}
}
impl From<FromUtf8Error> for Error {
fn from(e: FromUtf8Error) -> Self {
Self::UTF8(e)
}
}
impl From<bitcoin::hashes::Error> for Error {
fn from(e: bitcoin::hashes::Error) -> Self {
Self::Hash(e)
}
}
impl From<key::Error> for Error {
fn from(e: key::Error) -> Self {
Self::Keys(e)
}
}
impl From<id::Error> for Error {
fn from(e: id::Error) -> Self {
Self::EventId(e)
}
}
#[cfg(feature = "nip49")]
impl From<nip49::Error> for Error {
fn from(e: nip49::Error) -> Self {
Self::NIP49(e)
}
}
enum Nip19Prefix {
NSec,
#[cfg(feature = "nip49")]
NCryptSec,
NPub,
Note,
NProfile,
NEvent,
NAddr,
}
impl FromStr for Nip19Prefix {
type Err = Error;
fn from_str(value: &str) -> Result<Self, Self::Err> {
match value {
PREFIX_BECH32_SECRET_KEY => Ok(Nip19Prefix::NSec),
#[cfg(feature = "nip49")]
PREFIX_BECH32_SECRET_KEY_ENCRYPTED => Ok(Nip19Prefix::NCryptSec),
PREFIX_BECH32_PUBLIC_KEY => Ok(Nip19Prefix::NPub),
PREFIX_BECH32_NOTE_ID => Ok(Nip19Prefix::Note),
PREFIX_BECH32_PROFILE => Ok(Nip19Prefix::NProfile),
PREFIX_BECH32_EVENT => Ok(Nip19Prefix::NEvent),
PREFIX_BECH32_PARAMETERIZED_REPLACEABLE_EVENT => Ok(Nip19Prefix::NAddr),
_ => Err(Error::WrongPrefixOrVariant),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Nip19 {
Secret(SecretKey),
#[cfg(feature = "nip49")]
EncryptedSecret(EncryptedSecretKey),
Pubkey(PublicKey),
Profile(Nip19Profile),
EventId(EventId),
Event(Nip19Event),
Coordinate(Coordinate),
}
pub trait FromBech32: Sized {
type Err;
fn from_bech32<S>(s: S) -> Result<Self, Self::Err>
where
S: AsRef<str>;
}
pub trait ToBech32 {
type Err;
fn to_bech32(&self) -> Result<String, Self::Err>;
}
impl FromBech32 for Nip19 {
type Err = Error;
fn from_bech32<S>(hash: S) -> Result<Self, Self::Err>
where
S: AsRef<str>,
{
let (hrp, data, checksum) = bech32::decode(hash.as_ref())?;
let prefix: Nip19Prefix = Nip19Prefix::from_str(&hrp)?;
if checksum != Variant::Bech32 {
return Err(Error::WrongPrefixOrVariant);
}
let data: Vec<u8> = Vec::<u8>::from_base32(&data)?;
match prefix {
Nip19Prefix::NSec => Ok(Self::Secret(SecretKey::from_slice(data.as_slice())?)),
#[cfg(feature = "nip49")]
Nip19Prefix::NCryptSec => Ok(Self::EncryptedSecret(EncryptedSecretKey::from_slice(
data.as_slice(),
)?)),
Nip19Prefix::NPub => Ok(Self::Pubkey(PublicKey::from_slice(data.as_slice())?)),
Nip19Prefix::NProfile => Ok(Self::Profile(Nip19Profile::from_bech32_data(data)?)),
Nip19Prefix::NEvent => Ok(Self::Event(Nip19Event::from_bech32_data(data)?)),
Nip19Prefix::Note => Ok(Self::EventId(EventId::from_slice(data.as_slice())?)),
Nip19Prefix::NAddr => Ok(Self::Coordinate(Coordinate::from_bech32_data(data)?)),
}
}
}
impl ToBech32 for Nip19 {
type Err = Error;
fn to_bech32(&self) -> Result<String, Self::Err> {
match self {
Nip19::Secret(sec) => sec.to_bech32(),
#[cfg(feature = "nip49")]
Nip19::EncryptedSecret(cryptsec) => cryptsec.to_bech32(),
Nip19::Pubkey(pubkey) => pubkey.to_bech32(),
Nip19::Event(event) => event.to_bech32(),
Nip19::Profile(profile) => profile.to_bech32(),
Nip19::EventId(event_id) => event_id.to_bech32(),
Nip19::Coordinate(coordinate) => coordinate.to_bech32(),
}
}
}
impl FromBech32 for SecretKey {
type Err = Error;
fn from_bech32<S>(secret_key: S) -> Result<Self, Self::Err>
where
S: AsRef<str>,
{
let (hrp, data, checksum) = bech32::decode(secret_key.as_ref())?;
if hrp != PREFIX_BECH32_SECRET_KEY || checksum != Variant::Bech32 {
return Err(Error::WrongPrefixOrVariant);
}
let data = Vec::<u8>::from_base32(&data)?;
Ok(Self::from_slice(data.as_slice())?)
}
}
impl ToBech32 for SecretKey {
type Err = Error;
fn to_bech32(&self) -> Result<String, Self::Err> {
let data = self.secret_bytes().to_base32();
Ok(bech32::encode(
PREFIX_BECH32_SECRET_KEY,
data,
Variant::Bech32,
)?)
}
}
#[cfg(feature = "nip49")]
impl FromBech32 for EncryptedSecretKey {
type Err = Error;
fn from_bech32<S>(secret_key: S) -> Result<Self, Self::Err>
where
S: AsRef<str>,
{
let (hrp, data, checksum) = bech32::decode(secret_key.as_ref())?;
if hrp != PREFIX_BECH32_SECRET_KEY_ENCRYPTED || checksum != Variant::Bech32 {
return Err(Error::WrongPrefixOrVariant);
}
let data = Vec::<u8>::from_base32(&data)?;
Ok(Self::from_slice(data.as_slice())?)
}
}
#[cfg(feature = "nip49")]
impl ToBech32 for EncryptedSecretKey {
type Err = Error;
fn to_bech32(&self) -> Result<String, Self::Err> {
let data = self.as_vec().to_base32();
Ok(bech32::encode(
PREFIX_BECH32_SECRET_KEY_ENCRYPTED,
data,
Variant::Bech32,
)?)
}
}
impl FromBech32 for PublicKey {
type Err = Error;
fn from_bech32<S>(public_key: S) -> Result<Self, Self::Err>
where
S: AsRef<str>,
{
let (hrp, data, checksum) = bech32::decode(public_key.as_ref())?;
if hrp != PREFIX_BECH32_PUBLIC_KEY || checksum != Variant::Bech32 {
return Err(Error::WrongPrefixOrVariant);
}
let data = Vec::<u8>::from_base32(&data)?;
Ok(Self::from_slice(data.as_slice())?)
}
}
impl ToBech32 for PublicKey {
type Err = Error;
fn to_bech32(&self) -> Result<String, Self::Err> {
let data = self.serialize().to_base32();
Ok(bech32::encode(
PREFIX_BECH32_PUBLIC_KEY,
data,
Variant::Bech32,
)?)
}
}
impl FromBech32 for EventId {
type Err = Error;
fn from_bech32<S>(hash: S) -> Result<Self, Self::Err>
where
S: AsRef<str>,
{
let (hrp, data, checksum) = bech32::decode(hash.as_ref())?;
if hrp != PREFIX_BECH32_NOTE_ID || checksum != Variant::Bech32 {
return Err(Error::WrongPrefixOrVariant);
}
let data: Vec<u8> = Vec::<u8>::from_base32(&data)?;
Ok(Self::from_slice(data.as_slice())?)
}
}
impl ToBech32 for EventId {
type Err = Error;
fn to_bech32(&self) -> Result<String, Self::Err> {
let data = self.to_base32();
Ok(bech32::encode(
PREFIX_BECH32_NOTE_ID,
data,
Variant::Bech32,
)?)
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct Nip19Event {
pub event_id: EventId,
pub author: Option<PublicKey>,
pub relays: Vec<String>,
}
impl Nip19Event {
pub fn new<I, S>(event_id: EventId, relays: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
Self {
event_id,
author: None,
relays: relays.into_iter().map(|u| u.into()).collect(),
}
}
fn from_bech32_data(mut data: Vec<u8>) -> Result<Self, Error> {
let mut event_id: Option<EventId> = None;
let mut author: Option<PublicKey> = None;
let mut relays: Vec<String> = Vec::new();
while !data.is_empty() {
let t = data.first().ok_or(Error::TLV)?;
let l = data.get(1).ok_or(Error::TLV)?;
let l = *l as usize;
let bytes = data.get(2..l + 2).ok_or(Error::TLV)?;
match *t {
SPECIAL => {
if event_id.is_none() {
event_id = Some(EventId::from_slice(bytes)?);
}
}
AUTHOR => {
if author.is_none() {
author = Some(PublicKey::from_slice(bytes)?);
}
}
RELAY => {
relays.push(String::from_utf8(bytes.to_vec())?);
}
_ => (),
};
data.drain(..l + 2);
}
Ok(Self {
event_id: event_id.ok_or_else(|| Error::FieldMissing("event id".to_string()))?,
author,
relays,
})
}
}
impl FromBech32 for Nip19Event {
type Err = Error;
fn from_bech32<S>(s: S) -> Result<Self, Self::Err>
where
S: AsRef<str>,
{
let (hrp, data, checksum) = bech32::decode(s.as_ref())?;
if hrp != PREFIX_BECH32_EVENT || checksum != Variant::Bech32 {
return Err(Error::WrongPrefixOrVariant);
}
let data: Vec<u8> = Vec::from_base32(&data)?;
Self::from_bech32_data(data)
}
}
impl ToBech32 for Nip19Event {
type Err = Error;
fn to_bech32(&self) -> Result<String, Self::Err> {
let mut bytes: Vec<u8> = vec![SPECIAL, 32];
bytes.extend(self.event_id.inner().as_byte_array());
for relay in self.relays.iter() {
bytes.extend([RELAY, relay.len() as u8]);
bytes.extend(relay.as_bytes());
}
let data = bytes.to_base32();
Ok(bech32::encode(PREFIX_BECH32_EVENT, data, Variant::Bech32)?)
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct Nip19Profile {
pub public_key: PublicKey,
pub relays: Vec<Url>,
}
impl Nip19Profile {
pub fn new<I, U>(public_key: PublicKey, relays: I) -> Result<Self, Error>
where
I: IntoIterator<Item = U>,
U: TryIntoUrl,
Error: From<<U as TryIntoUrl>::Err>,
{
Ok(Self {
public_key,
relays: relays
.into_iter()
.map(|u| u.try_into_url())
.collect::<Result<Vec<Url>, _>>()?,
})
}
fn from_bech32_data(mut data: Vec<u8>) -> Result<Self, Error> {
let mut public_key: Option<PublicKey> = None;
let mut relays: Vec<Url> = Vec::new();
while !data.is_empty() {
let t = data.first().ok_or(Error::TLV)?;
let l = data.get(1).ok_or(Error::TLV)?;
let l = *l as usize;
let bytes = data.get(2..l + 2).ok_or(Error::TLV)?;
match *t {
SPECIAL => {
if public_key.is_none() {
public_key = Some(PublicKey::from_slice(bytes)?);
}
}
RELAY => {
let url = String::from_utf8(bytes.to_vec())?;
let url = Url::parse(&url)?;
relays.push(url);
}
_ => (),
};
data.drain(..l + 2);
}
Ok(Self {
public_key: public_key.ok_or_else(|| Error::FieldMissing("pubkey".to_string()))?,
relays,
})
}
}
impl ToBech32 for Nip19Profile {
type Err = Error;
fn to_bech32(&self) -> Result<String, Self::Err> {
let mut bytes: Vec<u8> = vec![SPECIAL, 32];
bytes.extend(self.public_key.serialize());
for relay in self.relays.iter() {
bytes.extend([RELAY, relay.as_str().len() as u8]);
bytes.extend(relay.as_ref().as_bytes());
}
let data = bytes.to_base32();
Ok(bech32::encode(
PREFIX_BECH32_PROFILE,
data,
Variant::Bech32,
)?)
}
}
impl FromBech32 for Nip19Profile {
type Err = Error;
fn from_bech32<S>(s: S) -> Result<Self, Self::Err>
where
S: AsRef<str>,
{
let (hrp, data, checksum) = bech32::decode(s.as_ref())?;
if hrp != PREFIX_BECH32_PROFILE || checksum != Variant::Bech32 {
return Err(Error::WrongPrefixOrVariant);
}
let data: Vec<u8> = Vec::from_base32(&data)?;
Self::from_bech32_data(data)
}
}
impl Coordinate {
fn from_bech32_data(mut data: Vec<u8>) -> Result<Self, Error> {
let mut identifier: Option<String> = None;
let mut pubkey: Option<PublicKey> = None;
let mut kind: Option<Kind> = None;
let mut relays: Vec<String> = Vec::new();
while !data.is_empty() {
let t = data.first().ok_or(Error::TLV)?;
let l = data.get(1).ok_or(Error::TLV)?;
let l = *l as usize;
let bytes: &[u8] = data.get(2..l + 2).ok_or(Error::TLV)?;
match *t {
SPECIAL => {
if identifier.is_none() {
identifier = Some(String::from_utf8(bytes.to_vec())?);
}
}
RELAY => {
relays.push(String::from_utf8(bytes.to_vec())?);
}
AUTHOR => {
if pubkey.is_none() {
pubkey = Some(PublicKey::from_slice(bytes)?);
}
}
KIND => {
if kind.is_none() {
let k: u64 =
u32::from_be_bytes(bytes.try_into().map_err(|_| Error::TryFromSlice)?)
as u64;
kind = Some(Kind::from(k));
}
}
_ => (),
};
data.drain(..l + 2);
}
Ok(Self {
kind: kind.ok_or_else(|| Error::FieldMissing("kind".to_string()))?,
public_key: pubkey.ok_or_else(|| Error::FieldMissing("pubkey".to_string()))?,
identifier: identifier.ok_or_else(|| Error::FieldMissing("identifier".to_string()))?,
relays,
})
}
}
impl FromBech32 for Coordinate {
type Err = Error;
fn from_bech32<S>(s: S) -> Result<Self, Self::Err>
where
S: AsRef<str>,
{
let (hrp, data, checksum) = bech32::decode(s.as_ref())?;
if hrp != PREFIX_BECH32_PARAMETERIZED_REPLACEABLE_EVENT || checksum != Variant::Bech32 {
return Err(Error::WrongPrefixOrVariant);
}
let data: Vec<u8> = Vec::from_base32(&data)?;
Self::from_bech32_data(data)
}
}
impl ToBech32 for Coordinate {
type Err = Error;
fn to_bech32(&self) -> Result<String, Self::Err> {
let mut bytes: Vec<u8> = Vec::new();
bytes.extend([SPECIAL, self.identifier.len() as u8]);
bytes.extend(self.identifier.as_bytes());
for relay in self.relays.iter() {
bytes.extend([RELAY, relay.len() as u8]);
bytes.extend(relay.as_bytes());
}
bytes.extend([AUTHOR, 32]);
bytes.extend(self.public_key.serialize());
bytes.extend([KIND, 4]);
bytes.extend(self.kind.as_u32().to_be_bytes());
let data = bytes.to_base32();
Ok(bech32::encode(
PREFIX_BECH32_PARAMETERIZED_REPLACEABLE_EVENT,
data,
Variant::Bech32,
)?)
}
}
#[cfg(test)]
mod tests {
use core::str::FromStr;
use super::*;
#[test]
fn to_bech32_public_key() {
let public_key =
PublicKey::from_str("aa4fc8665f5696e33db7e1a572e3b0f5b3d615837b0f362dcb1c8068b098c7b4")
.unwrap();
assert_eq!(
"npub14f8usejl26twx0dhuxjh9cas7keav9vr0v8nvtwtrjqx3vycc76qqh9nsy".to_string(),
public_key.to_bech32().unwrap()
);
}
#[test]
fn to_bech32_secret_key() {
let secret_key =
SecretKey::from_str("9571a568a42b9e05646a349c783159b906b498119390df9a5a02667155128028")
.unwrap();
assert_eq!(
"nsec1j4c6269y9w0q2er2xjw8sv2ehyrtfxq3jwgdlxj6qfn8z4gjsq5qfvfk99".to_string(),
secret_key.to_bech32().unwrap()
);
}
#[test]
fn to_bech32_note() {
let event_id =
EventId::from_hex("d94a3f4dd87b9a3b0bed183b32e916fa29c8020107845d1752d72697fe5309a5")
.unwrap();
assert_eq!(
"note1m99r7nwc0wdrkzldrqan96gklg5usqspq7z9696j6unf0ljnpxjspqfw99".to_string(),
event_id.to_bech32().unwrap()
);
}
#[test]
fn from_bech32_nip19_event() {
let expected_event_id =
EventId::from_hex("d94a3f4dd87b9a3b0bed183b32e916fa29c8020107845d1752d72697fe5309a5")
.unwrap();
let nip19 =
Nip19::from_bech32("note1m99r7nwc0wdrkzldrqan96gklg5usqspq7z9696j6unf0ljnpxjspqfw99")
.unwrap();
assert_eq!(Nip19::EventId(expected_event_id), nip19);
}
#[test]
fn from_bech32_nip19_profile() {
let expected_pubkey =
PublicKey::from_str("32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245")
.unwrap();
let nprofile = "nprofile1qqsr9cvzwc652r4m83d86ykplrnm9dg5gwdvzzn8ameanlvut35wy3gpz4mhxue69uhhyetvv9ujuerpd46hxtnfduhsz4nxck";
let nip19 = Nip19::from_bech32(nprofile).unwrap();
assert_eq!(
Nip19::Profile(Nip19Profile::new(expected_pubkey, ["wss://relay.damus.io/"]).unwrap()),
nip19
);
assert_eq!(nip19.to_bech32().unwrap(), nprofile);
}
#[test]
fn from_bech32_nevent_author() {
let expected_pubkey =
PublicKey::from_str("32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245")
.unwrap();
let nevent = "nevent1qqsdhet4232flykq3048jzc9msmaa3hnxuesxy3lnc33vd0wt9xwk6szyqewrqnkx4zsaweutf739s0cu7et29zrntqs5elw70vlm8zudr3y24sqsgy";
let event = Nip19Event::from_bech32(nevent).unwrap();
assert_eq!(event.author, Some(expected_pubkey));
}
}