Skip to content

Commit

Permalink
keyring: add nostr-keyring
Browse files Browse the repository at this point in the history
Closes #392

Signed-off-by: Yuki Kishimoto <[email protected]>
  • Loading branch information
yukibtc committed Apr 9, 2024
1 parent 6aae783 commit 1e0842d
Show file tree
Hide file tree
Showing 7 changed files with 274 additions and 0 deletions.
36 changes: 36 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 20 additions & 0 deletions crates/nostr-keyring/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
[package]
name = "nostr-keyring"
version = "0.29.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"
15 changes: 15 additions & 0 deletions crates/nostr-keyring/README.md
Original file line number Diff line number Diff line change
@@ -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
9 changes: 9 additions & 0 deletions crates/nostr-keyring/src/constants.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// Copyright (c) 2022-2023 Yuki Kishimoto
// Copyright (c) 2023-2024 Rust Nostr Developers
// Distributed under the MIT software license

pub const ACCOUNT: u8 = 0;
pub const NAME: u8 = 1;
pub const ACCOUNT_KEY_UNENCRYPTED: u8 = 2;
pub const ACCOUNT_KEY_ENCRYPTED: u8 = 3;
pub const ACCOUNT_KEY_WATCH_ONLY: u8 = 4;
68 changes: 68 additions & 0 deletions crates/nostr-keyring/src/dir.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// Copyright (c) 2022-2023 Yuki Kishimoto
// Copyright (c) 2023-2024 Rust Nostr Developers
// Distributed under the MIT software license

use std::collections::BTreeSet;
use std::fs;
use std::path::{Path, PathBuf};

use thiserror::Error;

pub(crate) const ACCOUNT_EXTENSION: &str = "ncryptsec";
pub(crate) const ACCOUNT_DOT_EXTENSION: &str = ".ncryptsec";

#[derive(Debug, Error)]
pub enum Error {
#[error(transparent)]
IO(#[from] std::io::Error),
#[error("Impossible to get file name")]
FailedToGetFileName,
}

pub fn accounts_dir<P>(base_path: P) -> Result<PathBuf, Error>
where
P: AsRef<Path>,
{
let base_path: &Path = base_path.as_ref();
fs::create_dir_all(&base_path)?;

let accounts_path: PathBuf = base_path.join("keys");
Ok(accounts_path)
}

pub fn get_accounts_list<P>(path: P) -> Result<BTreeSet<String>, Error>
where
P: AsRef<Path>,
{
let mut names: BTreeSet<String> = BTreeSet::new();

// Get and iterate all paths
let paths = fs::read_dir(path)?;
for path in paths {
let path: PathBuf = path?.path();

// Check if path has file name
if let Some(name) = path.file_name().and_then(|n| n.to_str()) {
// Check if file name terminate with extension
if name.ends_with(ACCOUNT_DOT_EXTENSION) {
// Split file name and extension
let mut split = name.split(ACCOUNT_DOT_EXTENSION);
if let Some(value) = split.next() {
names.insert(value.to_string());
}
}
}
}

Ok(names)
}

pub(crate) fn get_account_file<P, S>(base_path: P, name: S) -> Result<PathBuf, Error>
where
P: AsRef<Path>,
S: Into<String>,
{
let mut keychain_file: PathBuf = base_path.as_ref().join(name.into());
keychain_file.set_extension(ACCOUNT_EXTENSION);
Ok(keychain_file)
}
25 changes: 25 additions & 0 deletions crates/nostr-keyring/src/format.rs
Original file line number Diff line number Diff line change
@@ -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::prelude::*;

pub enum Version {
V1,
}

pub struct NostrKeyringDat {
version: Version,
list: Vec<Account>,
}

pub struct Account {
name: String,
key: AccountKey,
}

pub enum AccountKey {
Unencrypted(SecretKey),
Encrypted(EncryptedSecretKey),
WatchOnly(PublicKey),
}
101 changes: 101 additions & 0 deletions crates/nostr-keyring/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
// Copyright (c) 2022-2023 Yuki Kishimoto
// Copyright (c) 2023-2024 Rust Nostr Developers
// Distributed under the MIT software license

#![forbid(unsafe_code)]
#![warn(missing_docs)]
#![warn(rustdoc::bare_urls)]

//! Nostr Keyring
// * Save in OS keyring?
// * Save in ~/.nostr/accounts.dat or ~/.nostr/keys.dat

use std::collections::BTreeMap;
use std::path::{Path, PathBuf};

use nostr::prelude::*;
use thiserror::Error;
use crate::format::AccountKey;

mod constants;
mod dir;
mod format;

/// Nostr Keyring error
#[derive(Debug, Error)]
pub enum Error {
/// Dir error
#[error(transparent)]
Dir(#[from] dir::Error),
/// Can't get home directory
#[error("Can't get home directory")]
CantGetHomeDir,
}

pub enum Version {
V1,
}

pub struct NostrKeyring {
path: PathBuf,
version: Version,
accounts: BTreeMap<String, AccountKey>,
}

impl NostrKeyring {
/// Get list of available accounts
#[cfg(not(all(target_os = "android", target_os = "ios")))]
pub fn open() -> Result<Self, Error> {
let home_dir: PathBuf = dirs::home_dir().ok_or(Error::CantGetHomeDir)?;
todo!()
}

/// Open Nostr Keyring from custom path
pub fn open_in<P>(base_path: P) -> Result<Self, Error>
where
P: AsRef<Path>,
{
todo!()
}

/// Create new empty nostr keyring
///
/// Return error if already exists
#[cfg(not(all(target_os = "android", target_os = "ios")))]
pub fn create() -> Result<Self, Error> {
let home_dir: PathBuf = dirs::home_dir().ok_or(Error::CantGetHomeDir)?;
todo!()
}

/// Create new empty nostr keyring in custom path
pub fn create_in<P>(base_path: P) -> Result<Self, Error>
where
P: AsRef<Path>,
{
todo!()
}

/// Get list of available accounts
#[inline(always)]
pub fn list_accounts(&self) -> &BTreeMap<String, AccountKey> {
&self.accounts
}

/// Add account
#[inline(always)]
pub fn add_account(&mut self, name: String, key: AccountKey) {
self.accounts.insert(name, key);
}

/// Remove account from keyring
#[inline(always)]
pub fn remove_account(&mut self, name: &str) {
self.accounts.remove(name);
}

/// Write keyring to file
pub fn save(&self) -> Result<(), Error> {
todo!()
}
}

0 comments on commit 1e0842d

Please sign in to comment.