From 770d80421ee7f5084475a089a09257317bbb06b4 Mon Sep 17 00:00:00 2001 From: Yuki Kishimoto Date: Fri, 5 Apr 2024 10:41:24 +0200 Subject: [PATCH] keyring: add `nostr-keyring` Closes https://github.com/rust-nostr/nostr/issues/392 Signed-off-by: Yuki Kishimoto --- Cargo.lock | 36 ++++ crates/nostr-keyring/Cargo.toml | 20 ++ crates/nostr-keyring/README.md | 15 ++ crates/nostr-keyring/examples/keyring.rs | 25 +++ crates/nostr-keyring/src/account.rs | 79 +++++++ crates/nostr-keyring/src/constants.rs | 12 ++ crates/nostr-keyring/src/dat.rs | 257 +++++++++++++++++++++++ crates/nostr-keyring/src/error.rs | 45 ++++ crates/nostr-keyring/src/lib.rs | 152 ++++++++++++++ crates/nostr-keyring/src/prelude.rs | 13 ++ crates/nostr-keyring/src/version.rs | 25 +++ justfile | 2 +- 12 files changed, 680 insertions(+), 1 deletion(-) create mode 100644 crates/nostr-keyring/Cargo.toml create mode 100644 crates/nostr-keyring/README.md create mode 100644 crates/nostr-keyring/examples/keyring.rs create mode 100644 crates/nostr-keyring/src/account.rs create mode 100644 crates/nostr-keyring/src/constants.rs create mode 100644 crates/nostr-keyring/src/dat.rs create mode 100644 crates/nostr-keyring/src/error.rs create mode 100644 crates/nostr-keyring/src/lib.rs create mode 100644 crates/nostr-keyring/src/prelude.rs create mode 100644 crates/nostr-keyring/src/version.rs diff --git a/Cargo.lock b/Cargo.lock index 7c5c7c0c9..2ee2d3641 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -763,6 +763,15 @@ dependencies = [ "subtle", ] +[[package]] +name = "dirs" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" +dependencies = [ + "dirs-sys", +] + [[package]] name = "dirs-next" version = "2.0.0" @@ -773,6 +782,18 @@ dependencies = [ "dirs-sys-next", ] +[[package]] +name = "dirs-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.48.0", +] + [[package]] name = "dirs-sys-next" version = "0.1.2" @@ -1700,6 +1721,15 @@ dependencies = [ "wasm-bindgen-futures", ] +[[package]] +name = "nostr-keyring" +version = "0.32.0" +dependencies = [ + "dirs", + "nostr", + "thiserror", +] + [[package]] name = "nostr-ndb" version = "0.32.0" @@ -1931,6 +1961,12 @@ dependencies = [ "log", ] +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + [[package]] name = "overload" version = "0.1.1" diff --git a/crates/nostr-keyring/Cargo.toml b/crates/nostr-keyring/Cargo.toml new file mode 100644 index 000000000..029489104 --- /dev/null +++ b/crates/nostr-keyring/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "nostr-keyring" +version = "0.32.0" +edition = "2021" +description = "Nostr Keyring" +authors.workspace = true +homepage.workspace = true +repository.workspace = true +license.workspace = true +readme = "README.md" +rust-version.workspace = true +keywords = ["nostr", "keyring"] + +[dependencies] +#keyring = "2.3" # TODO: use only for ios target? +nostr = { workspace = true, default-features = false, features = ["std", "nip49"] } +thiserror.workspace = true + +[target.'cfg(not(all(target_os = "android", target_os = "ios")))'.dependencies] +dirs = "5.0" diff --git a/crates/nostr-keyring/README.md b/crates/nostr-keyring/README.md new file mode 100644 index 000000000..6573b261c --- /dev/null +++ b/crates/nostr-keyring/README.md @@ -0,0 +1,15 @@ +# Nostr Keyring + +Keyring for Nostr apps. + +## State + +**This library is in an ALPHA state**, things that are implemented generally work but the API will change in breaking ways. + +## Donations + +`rust-nostr` is free and open-source. This means we do not earn any revenue by selling it. Instead, we rely on your financial support. If you actively use any of the `rust-nostr` libs/software/services, then please [donate](https://rust-nostr.org/donate). + +## License + +This project is distributed under the MIT software license - see the [LICENSE](../../LICENSE) file for details \ No newline at end of file diff --git a/crates/nostr-keyring/examples/keyring.rs b/crates/nostr-keyring/examples/keyring.rs new file mode 100644 index 000000000..a97dc2020 --- /dev/null +++ b/crates/nostr-keyring/examples/keyring.rs @@ -0,0 +1,25 @@ +// Copyright (c) 2022-2023 Yuki Kishimoto +// Copyright (c) 2023-2024 Rust Nostr Developers +// Distributed under the MIT software license + +use nostr_keyring::prelude::*; + +fn main() { + let mut keyring = NostrKeyring::open().unwrap(); + + let keys = + Keys::parse("nsec12kcgs78l06p30jz7z7h3n2x2cy99nw2z6zspjdp7qc206887mwvs95lnkx").unwrap(); + let account = Account::new( + "Test", + keys.public_key(), + AccountSecretKey::Unencrypted(keys.secret_key().unwrap().clone()), + ); + keyring.add_account(account).unwrap(); + + // Get accounts + for (index, account) in keyring.accounts().iter().enumerate() { + println!("Account #{index}:"); + println!("- Name: {}", account.name()); + println!("- Public Key: {}", account.public_key()); + } +} diff --git a/crates/nostr-keyring/src/account.rs b/crates/nostr-keyring/src/account.rs new file mode 100644 index 000000000..4fd7fcc1a --- /dev/null +++ b/crates/nostr-keyring/src/account.rs @@ -0,0 +1,79 @@ +// Copyright (c) 2022-2023 Yuki Kishimoto +// Copyright (c) 2023-2024 Rust Nostr Developers +// Distributed under the MIT software license + +use std::cmp::Ordering; + +use nostr::prelude::*; + +/// Secret Key +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum AccountSecretKey { + /// Encrypted + Encrypted(EncryptedSecretKey), + /// Unencrypted + Unencrypted(SecretKey), +} + +/// Account +#[derive(Debug, Clone)] +pub struct Account { + pub(crate) name: String, + pub(crate) public_key: PublicKey, + pub(crate) secret_key: AccountSecretKey, // TODO: allow only encrypted secret key? +} + +impl PartialEq for Account { + fn eq(&self, other: &Self) -> bool { + self.public_key == other.public_key + } +} + +impl Eq for Account {} + +impl PartialOrd for Account { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for Account { + fn cmp(&self, other: &Self) -> Ordering { + // Sort ASC by name. + // If name are equals, sort by public key. + if self.name != other.name { + self.name.cmp(&other.name) + } else { + self.public_key.cmp(&other.public_key) + } + } +} + +impl Account { + #[inline] + pub fn new(name: S, public_key: PublicKey, secret_key: AccountSecretKey) -> Self + where + S: Into, + { + Self { + name: name.into(), + public_key, + secret_key, + } + } + + #[inline] + pub fn name(&self) -> &str { + &self.name + } + + #[inline] + pub fn public_key(&self) -> &PublicKey { + &self.public_key + } + + #[inline] + pub fn secret_key(&self) -> &AccountSecretKey { + &self.secret_key + } +} diff --git a/crates/nostr-keyring/src/constants.rs b/crates/nostr-keyring/src/constants.rs new file mode 100644 index 000000000..7e427959f --- /dev/null +++ b/crates/nostr-keyring/src/constants.rs @@ -0,0 +1,12 @@ +// Copyright (c) 2022-2023 Yuki Kishimoto +// Copyright (c) 2023-2024 Rust Nostr Developers +// Distributed under the MIT software license + +pub const DEFAULT_FILE_NAME: &str = "keyring"; +pub const EXTENSION: &str = "dat"; + +pub const ACCOUNT: u8 = 0x00; +pub const NAME: u8 = 0x01; +pub const PUBLIC_KEY: u8 = 0x02; +pub const SECRET_KEY_ENCRYPTED: u8 = 0x03; +pub const SECRET_KEY_UNENCRYPTED: u8 = 0x04; diff --git a/crates/nostr-keyring/src/dat.rs b/crates/nostr-keyring/src/dat.rs new file mode 100644 index 000000000..66569b361 --- /dev/null +++ b/crates/nostr-keyring/src/dat.rs @@ -0,0 +1,257 @@ +// Copyright (c) 2022-2023 Yuki Kishimoto +// Copyright (c) 2023-2024 Rust Nostr Developers +// Distributed under the MIT software license + +//! Keyring.dat file (TLV format) + +use std::collections::BTreeSet; + +use nostr::prelude::*; + +use super::constants::{ACCOUNT, NAME, PUBLIC_KEY, SECRET_KEY_ENCRYPTED, SECRET_KEY_UNENCRYPTED}; +use super::{Error, Version}; +use crate::account::{Account, AccountSecretKey}; +use crate::error::TlvError; + +#[derive(Debug, Clone, Default, PartialEq, Eq)] +pub struct NostrKeyringIntermediate { + pub version: Version, + pub accounts: BTreeSet, +} + +impl NostrKeyringIntermediate { + /// Parse keyring bytes + pub fn parse(mut bytes: &[u8]) -> Result { + if bytes.len() > 1 { + // Get version + // SAFETY: bytes len checked above + let version: Version = Version::try_from(bytes[0])?; + + // Remove version byte + bytes = &bytes[1..]; + + let mut accounts: BTreeSet = BTreeSet::new(); + + match version { + Version::V1 => { + parse_v1(bytes, &mut accounts)?; + } + } + + Ok(Self { version, accounts }) + } else { + Err(Error::InvalidKeyringLen) + } + } + + pub fn encode(&self) -> Vec { + let mut bytes: Vec = Vec::with_capacity(1); + + // Push version + bytes.push(self.version as u8); + + // Iter and encode accounts + for account in self.accounts.iter() { + let encoded: Vec = encode_account_v1(account); + bytes.push(ACCOUNT); + bytes.push(encoded.len() as u8); + bytes.extend(encoded); + } + + bytes + } +} + +fn parse_v1(mut bytes: &[u8], accounts: &mut BTreeSet) -> Result<(), Error> { + while !bytes.is_empty() { + // Get type, len and value + let t: &u8 = bytes.first().ok_or(Error::TLV(TlvError::Type))?; + let l: usize = bytes.get(1).copied().ok_or(Error::TLV(TlvError::Len))? as usize; + let v: &[u8] = bytes.get(2..l + 2).ok_or(Error::TLV(TlvError::Value))?; + + if t == &ACCOUNT { + let account: Account = parse_v1_account(v)?; + accounts.insert(account); + } else { + // TODO: return error? + eprintln!("unexpected type: {t}"); + } + + bytes = &bytes[l + 2..]; + } + + Ok(()) +} + +/// Parse account +fn parse_v1_account(mut value: &[u8]) -> Result { + let mut name: Option = None; + let mut public_key: Option = None; + let mut secret_key: Option = None; + + while !value.is_empty() { + // Get type, len and value + let t: &u8 = value.first().ok_or(Error::TLV(TlvError::Type))?; + let l: usize = value.get(1).copied().ok_or(Error::TLV(TlvError::Len))? as usize; + let v: &[u8] = value.get(2..l + 2).ok_or(Error::TLV(TlvError::Value))?; + + match t { + &NAME => { + if name.is_none() { + name = Some(String::from_utf8_lossy(v).to_string()); + } + } + &PUBLIC_KEY => { + if public_key.is_none() { + public_key = Some(PublicKey::from_slice(v)?); + } + } + &SECRET_KEY_ENCRYPTED => { + if secret_key.is_none() { + let encrypted_key: EncryptedSecretKey = EncryptedSecretKey::from_slice(v)?; + secret_key = Some(AccountSecretKey::Encrypted(encrypted_key)); + } + } + &SECRET_KEY_UNENCRYPTED => { + if secret_key.is_none() { + let sk: SecretKey = SecretKey::from_slice(v)?; + secret_key = Some(AccountSecretKey::Unencrypted(sk)); + } + } + _ => {} + } + + value = &value[l + 2..]; + } + + Ok(Account { + name: name.ok_or_else(|| Error::FieldMissing(String::from("name")))?, + public_key: public_key.ok_or_else(|| Error::FieldMissing(String::from("public key")))?, + secret_key: secret_key.ok_or_else(|| Error::FieldMissing(String::from("secret key")))?, + }) +} + +fn encode_account_v1(account: &Account) -> Vec { + let mut bytes: Vec = Vec::new(); + + // Name + bytes.push(NAME); + bytes.push(account.name.as_bytes().len() as u8); + bytes.extend(account.name.as_bytes()); + + // Public Key + bytes.push(PUBLIC_KEY); + bytes.push(account.public_key.to_bytes().len() as u8); + bytes.extend(account.public_key.to_bytes()); + + // Secret Key + match &account.secret_key { + AccountSecretKey::Encrypted(k) => { + let b: Vec = k.as_vec(); + bytes.push(SECRET_KEY_ENCRYPTED); + bytes.push(b.len() as u8); + bytes.extend(b); + } + AccountSecretKey::Unencrypted(k) => { + bytes.push(SECRET_KEY_UNENCRYPTED); + bytes.push(k.as_secret_bytes().len() as u8); + bytes.extend(k.as_secret_bytes()); + } + }; + + bytes +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_encode_decode_keyring() { + let mut accounts = BTreeSet::new(); + accounts.insert(Account { + name: String::from("Test"), + public_key: PublicKey::parse( + "npub12y7dhz8erxua6gyjvra8c0fj9wcm5g2vkzyflfjwa452k2eu9quslf2zze", + ) + .unwrap(), + secret_key: AccountSecretKey::Unencrypted( + SecretKey::parse("nsec12kcgs78l06p30jz7z7h3n2x2cy99nw2z6zspjdp7qc206887mwvs95lnkx") + .unwrap(), + ), + }); + accounts.insert(Account { + name: String::from("Test 2"), + public_key: PublicKey::parse("672a31bfc59d3f04548ec9b7daeeba2f61814e8ccc40448045007f5479f693a3").unwrap(), + secret_key: AccountSecretKey::Encrypted(EncryptedSecretKey::from_bech32("ncryptsec1qgg9947rlpvqu76pj5ecreduf9jxhselq2nae2kghhvd5g7dgjtcxfqtd67p9m0w57lspw8gsq6yphnm8623nsl8xn9j4jdzz84zm3frztj3z7s35vpzmqf6ksu8r89qk5z2zxfmu5gv8th8wclt0h4p").unwrap()), + }); + + let keyring = NostrKeyringIntermediate { + version: Version::V1, + accounts, + }; + + let encoded = keyring.encode(); + + let decoded = NostrKeyringIntermediate::parse(&encoded).unwrap(); + + assert_eq!(keyring, decoded); + } +} + +#[cfg(bench)] +mod benches { + use test::{black_box, Bencher}; + + use super::*; + + const KEYRING: [u8; 214] = [ + 1, 0, 74, 1, 4, 84, 101, 115, 116, 2, 32, 81, 60, 219, 136, 249, 25, 185, 221, 32, 146, 96, + 250, 124, 61, 50, 43, 177, 186, 33, 76, 176, 136, 159, 166, 78, 237, 104, 171, 43, 60, 40, + 57, 4, 32, 85, 176, 136, 120, 255, 126, 131, 23, 200, 94, 23, 175, 25, 168, 202, 193, 10, + 89, 185, 66, 208, 160, 25, 52, 62, 6, 20, 253, 28, 254, 219, 153, 0, 135, 1, 6, 84, 101, + 115, 116, 32, 50, 2, 32, 103, 42, 49, 191, 197, 157, 63, 4, 84, 142, 201, 183, 218, 238, + 186, 47, 97, 129, 78, 140, 204, 64, 68, 128, 69, 0, 127, 84, 121, 246, 147, 163, 3, 91, 2, + 16, 82, 215, 195, 248, 88, 14, 123, 65, 149, 51, 129, 229, 188, 73, 100, 107, 195, 63, 2, + 167, 220, 170, 200, 189, 216, 218, 35, 205, 68, 151, 131, 36, 11, 110, 188, 18, 237, 238, + 167, 191, 0, 184, 232, 128, 52, 64, 222, 123, 62, 149, 25, 195, 231, 52, 203, 42, 201, 162, + 17, 234, 45, 197, 35, 18, 229, 17, 122, 17, 163, 2, 45, 129, 58, 180, 56, 113, 156, 160, + 181, 4, 161, 25, 59, 229, 16, 195, 174, 231, 118, + ]; + + #[bench] + pub fn parse_keyring(bh: &mut Bencher) { + bh.iter(|| { + black_box(NostrKeyringIntermediate::parse(&KEYRING)).unwrap(); + }); + } + + #[bench] + pub fn encode_keyring(bh: &mut Bencher) { + let mut accounts = BTreeSet::new(); + accounts.insert(Account { + name: String::from("Test"), + public_key: PublicKey::parse( + "npub12y7dhz8erxua6gyjvra8c0fj9wcm5g2vkzyflfjwa452k2eu9quslf2zze", + ) + .unwrap(), + secret_key: AccountSecretKey::Unencrypted( + SecretKey::parse("nsec12kcgs78l06p30jz7z7h3n2x2cy99nw2z6zspjdp7qc206887mwvs95lnkx") + .unwrap(), + ), + }); + accounts.insert(Account { + name: String::from("Test 2"), + public_key: PublicKey::parse("672a31bfc59d3f04548ec9b7daeeba2f61814e8ccc40448045007f5479f693a3").unwrap(), + secret_key: AccountSecretKey::Encrypted(EncryptedSecretKey::from_bech32("ncryptsec1qgg9947rlpvqu76pj5ecreduf9jxhselq2nae2kghhvd5g7dgjtcxfqtd67p9m0w57lspw8gsq6yphnm8623nsl8xn9j4jdzz84zm3frztj3z7s35vpzmqf6ksu8r89qk5z2zxfmu5gv8th8wclt0h4p").unwrap()), + }); + let keyring = NostrKeyringIntermediate { + version: Version::V1, + accounts, + }; + + bh.iter(|| { + black_box(keyring.encode()); + }); + } +} diff --git a/crates/nostr-keyring/src/error.rs b/crates/nostr-keyring/src/error.rs new file mode 100644 index 000000000..fc43786fe --- /dev/null +++ b/crates/nostr-keyring/src/error.rs @@ -0,0 +1,45 @@ +// Copyright (c) 2022-2023 Yuki Kishimoto +// Copyright (c) 2023-2024 Rust Nostr Developers +// Distributed under the MIT software license + +use std::io; + +use nostr::prelude::*; +use thiserror::Error; + +/// Nostr Keyring error +#[derive(Debug, Error)] +pub enum Error { + /// I/O error + #[error(transparent)] + IO(#[from] io::Error), + /// Key error + #[error(transparent)] + Key(#[from] key::Error), + /// NIP-49 error + #[error(transparent)] + NIP49(#[from] nip49::Error), + /// Keyring data too short + #[error("Keyring data too short")] + InvalidKeyringLen, + /// Unknown keyring version + #[error("Unknown keyring version: {0}")] + UnknownVersion(u8), + /// TLV error + #[error("TLV (type-length-value) error: {0:?}")] + TLV(TlvError), + /// Field missing + #[error("Field missing: {0}")] + FieldMissing(String), + /// Can't get home directory + #[cfg(not(all(target_os = "android", target_os = "ios")))] + #[error("Can't get home directory")] + CantGetHomeDir, +} + +#[derive(Debug)] +pub enum TlvError { + Type, + Len, + Value, +} diff --git a/crates/nostr-keyring/src/lib.rs b/crates/nostr-keyring/src/lib.rs new file mode 100644 index 000000000..cc57fa2a1 --- /dev/null +++ b/crates/nostr-keyring/src/lib.rs @@ -0,0 +1,152 @@ +// Copyright (c) 2022-2023 Yuki Kishimoto +// Copyright (c) 2023-2024 Rust Nostr Developers +// Distributed under the MIT software license + +//! Nostr Keyring + +#![forbid(unsafe_code)] +#![warn(missing_docs)] +#![warn(rustdoc::bare_urls)] +#![cfg_attr(bench, feature(test))] + +#[cfg(bench)] +extern crate test; + +use std::collections::BTreeSet; +use std::fs::{self, File}; +use std::io::{Read, Write}; +use std::path::{Path, PathBuf}; + +use nostr::prelude::*; +use thiserror::Error; + +mod account; +mod constants; +mod dat; +mod error; +pub mod prelude; +mod version; + +pub use self::account::{Account, AccountSecretKey}; +use self::constants::{DEFAULT_FILE_NAME, EXTENSION}; +use self::dat::NostrKeyringIntermediate; +pub use self::error::Error; +pub use self::version::Version; + +/// Nostr Keyring +pub struct NostrKeyring { + path: PathBuf, + inner: NostrKeyringIntermediate, +} + +impl NostrKeyring { + /// Open keyring + /// + /// Will open the keyring at `$HOME/.nostr/keyring.dat`. + /// If not exists, will be created an empty one. + /// + /// |Platform | Path | + /// | ------- | ------------------------------------ | + /// | Linux | `/home//.nostr/keyring.dat` | + /// | macOS | `/Users//.nostr/keyring.dat` | + /// | Windows | `C:\Users\\.nostr\keyring.dat` | + #[cfg(not(all(target_os = "android", target_os = "ios")))] + pub fn open() -> Result { + let home_dir: PathBuf = dirs::home_dir().ok_or(Error::CantGetHomeDir)?; + let nostr_dir: PathBuf = home_dir.join(".nostr"); + Self::open_in(nostr_dir, None) + } + + /// Open Nostr Keyring from custom path + /// + /// If not exists, will be created an empty one. + pub fn open_in

(base_path: P, file_name: Option<&str>) -> Result + where + P: AsRef, + { + let base_path: &Path = base_path.as_ref(); + + // Create dirs if not exists + fs::create_dir_all(base_path)?; + + let file_name: &str = file_name.unwrap_or(DEFAULT_FILE_NAME); + let mut path: PathBuf = base_path.join(file_name); + + // Set file extension + path.set_extension(EXTENSION); + + // Check if `keyring.dat` file exists + if path.exists() && path.is_file() { + // Open file and read it + let mut file: File = File::open(&path)?; + let mut buffer: Vec = Vec::new(); + file.read_to_end(&mut buffer)?; + + Ok(Self { + path, + inner: NostrKeyringIntermediate::parse(&buffer)?, + }) + } else { + // Create empty keyring + Ok(Self { + path, + inner: NostrKeyringIntermediate::default(), + }) + } + } + + /// Get keyring version + #[inline] + pub fn version(&self) -> Version { + self.inner.version + } + + /// Get list of available accounts + #[inline] + pub fn accounts(&self) -> &BTreeSet { + &self.inner.accounts + } + + /// Get account by public key + #[inline] + pub fn account_by_public_key(&self, public_key: &PublicKey) -> Option<&Account> { + self.inner + .accounts + .iter() + .find(|a| &a.public_key == public_key) + } + + /// Add account to the keyring + /// + /// Automatically save the file. + #[inline] + pub fn add_account(&mut self, account: Account) -> Result<(), Error> { + // TODO: remove '&mut self'? + self.inner.accounts.insert(account); + self.save() + } + + /// Remove account from keyring + /// + /// Automatically save the file. + #[inline] + pub fn remove_account(&mut self, account: &Account) -> Result<(), Error> { + // TODO: remove '&mut self'? + self.inner.accounts.remove(account); + self.save() + } + + // TODO: bulk_add and bulk_remove + + /// Write keyring to file + pub fn save(&self) -> Result<(), Error> { + let bytes: Vec = self.inner.encode(); + let mut file: File = File::options() + .create(true) + .write(true) + .truncate(true) + .open(self.path.as_path())?; + file.write_all(&bytes)?; + Ok(()) + } +} diff --git a/crates/nostr-keyring/src/prelude.rs b/crates/nostr-keyring/src/prelude.rs new file mode 100644 index 000000000..9a7bb9ca9 --- /dev/null +++ b/crates/nostr-keyring/src/prelude.rs @@ -0,0 +1,13 @@ +// Copyright (c) 2022-2023 Yuki Kishimoto +// Copyright (c) 2023-2024 Rust Nostr Developers +// Distributed under the MIT software license + +//! Prelude + +#![allow(unknown_lints)] +#![allow(ambiguous_glob_reexports)] +#![doc(hidden)] + +pub use nostr::prelude::*; + +pub use crate::*; diff --git a/crates/nostr-keyring/src/version.rs b/crates/nostr-keyring/src/version.rs new file mode 100644 index 000000000..4f58ea640 --- /dev/null +++ b/crates/nostr-keyring/src/version.rs @@ -0,0 +1,25 @@ +// Copyright (c) 2022-2023 Yuki Kishimoto +// Copyright (c) 2023-2024 Rust Nostr Developers +// Distributed under the MIT software license + +use crate::Error; + +/// Nostr Keyring version +#[repr(u8)] +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum Version { + /// Version 1 + #[default] + V1 = 0x01, +} + +impl TryFrom for Version { + type Error = Error; + + fn try_from(version: u8) -> Result { + match version { + 0x01 => Ok(Self::V1), + v => Err(Error::UnknownVersion(v)), + } + } +} diff --git a/justfile b/justfile index 478ae59d7..71012d4d6 100755 --- a/justfile +++ b/justfile @@ -47,7 +47,7 @@ release: # Run benches (unstable) bench: - RUSTFLAGS='--cfg=bench' cargo +nightly bench -p nostr + RUSTFLAGS='--cfg=bench' cargo +nightly bench # Check cargo duplicate dependencies dup: