diff --git a/README.md b/README.md index bb4ac970..ed2bd388 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,19 @@ CDK is a collection of rust crates for [Cashu](https://github.com/cashubtc) wallets and mints written in Rust. -**ALPHA** This library is in early development, the api will change. +**ALPHA** This library is in early development, the api will change and should be used with caution. + + +## Project structure + +The project is split up into several crates in the `crates/` directory: + +* Libraries: + * [**cdk**](./crates/cdk/): Rust implementation of Cashu protocol. + * [**cdk-sqlite**](./crates/cdk-sqlite/): Sqlite Storage backend + * [**cdk-redb**](./crates/cdk-redb/): Redb Storage backend +* Binaries: + * [**cdk-cli**](./crates/cdk-cli/): Cashu wallet CLI ## Implemented [NUTs](https://github.com/cashubtc/nuts/): diff --git a/bindings/cdk-js/src/wallet.rs b/bindings/cdk-js/src/wallet.rs index 312496dc..182bce07 100644 --- a/bindings/cdk-js/src/wallet.rs +++ b/bindings/cdk-js/src/wallet.rs @@ -7,7 +7,7 @@ use cdk::amount::SplitTarget; use cdk::nuts::{Proofs, SecretKey}; use cdk::wallet::Wallet; use cdk::Amount; -use cdk_rexie::RexieWalletDatabase; +use cdk_rexie::WalletRexieDatabase; use wasm_bindgen::prelude::*; use crate::error::{into_err, Result}; @@ -42,7 +42,7 @@ impl From for JsWallet { impl JsWallet { #[wasm_bindgen(constructor)] pub async fn new(mints_url: String, unit: JsCurrencyUnit, seed: Vec) -> Self { - let db = RexieWalletDatabase::new().await.unwrap(); + let db = WalletRexieDatabase::new().await.unwrap(); Wallet::new(&mints_url, unit.into(), Arc::new(db), &seed).into() } diff --git a/crates/cdk-cli/src/main.rs b/crates/cdk-cli/src/main.rs index ec5c00be..af1b81dc 100644 --- a/crates/cdk-cli/src/main.rs +++ b/crates/cdk-cli/src/main.rs @@ -9,8 +9,8 @@ use bip39::Mnemonic; use cdk::cdk_database::WalletDatabase; use cdk::wallet::Wallet; use cdk::{cdk_database, UncheckedUrl}; -use cdk_redb::RedbWalletDatabase; -use cdk_sqlite::WalletSQLiteDatabase; +use cdk_redb::WalletRedbDatabase; +use cdk_sqlite::WalletSqliteDatabase; use clap::{Parser, Subcommand}; use rand::Rng; @@ -86,7 +86,7 @@ async fn main() -> Result<()> { match args.engine.as_str() { "sqlite" => { let sql_path = work_dir.join("cdk-cli.sqlite"); - let sql = WalletSQLiteDatabase::new(&sql_path).await?; + let sql = WalletSqliteDatabase::new(&sql_path).await?; sql.migrate().await; @@ -95,7 +95,7 @@ async fn main() -> Result<()> { "redb" => { let redb_path = work_dir.join("cdk-cli.redb"); - Arc::new(RedbWalletDatabase::new(&redb_path)?) + Arc::new(WalletRedbDatabase::new(&redb_path)?) } _ => bail!("Unknown DB engine"), }; diff --git a/crates/cdk-redb/README.md b/crates/cdk-redb/README.md new file mode 100644 index 00000000..75525b8a --- /dev/null +++ b/crates/cdk-redb/README.md @@ -0,0 +1,23 @@ + +# Cashu Development Kit Redb Storage Backend + +**ALPHA** This library is in early development, the api will change and should be used with caution. + +cdk-redb is the [redb](https://docs.rs/redb/latest/redb/) storage backend for cdk. + +## Crate Feature Flags + +The following crate feature flags are available: + +| Feature | Default | Description | +|-------------|:-------:|------------------------------------| +| `wallet` | Yes | Enable cashu wallet features | +| `mint` | Yes | Enable cashu mint wallet features | + +## Implemented [NUTs](https://github.com/cashubtc/nuts/): + +See + +## License + +This project is distributed under the MIT software license - see the [LICENSE](../../LICENSE) file for details diff --git a/crates/cdk-redb/src/error.rs b/crates/cdk-redb/src/error.rs index 848d09b5..ca8bfbc3 100644 --- a/crates/cdk-redb/src/error.rs +++ b/crates/cdk-redb/src/error.rs @@ -1,7 +1,10 @@ +//! Redb Error + use std::num::ParseIntError; use thiserror::Error; +/// Redb Database Error #[derive(Debug, Error)] pub enum Error { /// Redb Error diff --git a/crates/cdk-redb/src/lib.rs b/crates/cdk-redb/src/lib.rs index 47e58bcb..7942ef2e 100644 --- a/crates/cdk-redb/src/lib.rs +++ b/crates/cdk-redb/src/lib.rs @@ -1,3 +1,8 @@ +//! SQLite Storage backend for CDK + +#![warn(missing_docs)] +#![warn(rustdoc::bare_urls)] + pub mod error; mod migrations; @@ -9,4 +14,4 @@ pub mod wallet; #[cfg(feature = "mint")] pub use mint::MintRedbDatabase; #[cfg(feature = "wallet")] -pub use wallet::RedbWalletDatabase; +pub use wallet::WalletRedbDatabase; diff --git a/crates/cdk-redb/src/mint/mod.rs b/crates/cdk-redb/src/mint/mod.rs index 9c0a615e..e0d3a4f2 100644 --- a/crates/cdk-redb/src/mint/mod.rs +++ b/crates/cdk-redb/src/mint/mod.rs @@ -1,3 +1,5 @@ +//! SQLite Storage for CDK + use std::cmp::Ordering; use std::collections::HashMap; use std::path::Path; @@ -34,12 +36,14 @@ const BLINDED_SIGNATURES: TableDefinition<[u8; 33], &str> = const DATABASE_VERSION: u32 = 0; +/// Mint Redbdatabase #[derive(Debug, Clone)] pub struct MintRedbDatabase { db: Arc>, } impl MintRedbDatabase { + /// Create new [`MintRedbDatabase`] pub fn new(path: &Path) -> Result { { // Check database version diff --git a/crates/cdk-redb/src/wallet/mod.rs b/crates/cdk-redb/src/wallet/mod.rs index db411ceb..47f42208 100644 --- a/crates/cdk-redb/src/wallet/mod.rs +++ b/crates/cdk-redb/src/wallet/mod.rs @@ -41,12 +41,14 @@ const NOSTR_LAST_CHECKED: TableDefinition<&str, u32> = TableDefinition::new("key const DATABASE_VERSION: u32 = 1; +/// Wallet Redb Database #[derive(Debug, Clone)] -pub struct RedbWalletDatabase { +pub struct WalletRedbDatabase { db: Arc>, } -impl RedbWalletDatabase { +impl WalletRedbDatabase { + /// Create new [`WalletRedbDatabase`] pub fn new(path: &Path) -> Result { { let db = Arc::new(Database::create(path)?); @@ -132,7 +134,7 @@ impl RedbWalletDatabase { } #[async_trait] -impl WalletDatabase for RedbWalletDatabase { +impl WalletDatabase for WalletRedbDatabase { type Err = cdk_database::Error; #[instrument(skip(self))] diff --git a/crates/cdk-rexie/README.md b/crates/cdk-rexie/README.md new file mode 100644 index 00000000..a0c20186 --- /dev/null +++ b/crates/cdk-rexie/README.md @@ -0,0 +1,22 @@ + +# Cashu Development Kit Redb Storage Backend + +**ALPHA** This library is in early development, the api will change and should be used with caution. + +cdk-rexie is the [rexie](https://docs.rs/rexie/latest/rexie/) storage backend for wasm cdk wallets in the browser. + +## Crate Feature Flags + +The following crate feature flags are available: + +| Feature | Default | Description | +|-------------|:-------:|------------------------------------| +| `wallet` | Yes | Enable cashu wallet features | + +## Implemented [NUTs](https://github.com/cashubtc/nuts/): + +See + +## License + +This project is distributed under the MIT software license - see the [LICENSE](../../LICENSE) file for details. diff --git a/crates/cdk-rexie/src/lib.rs b/crates/cdk-rexie/src/lib.rs index 8d001668..fca5caad 100644 --- a/crates/cdk-rexie/src/lib.rs +++ b/crates/cdk-rexie/src/lib.rs @@ -1,5 +1,10 @@ +//! Rexie Indexdb database + +#![warn(missing_docs)] +#![warn(rustdoc::bare_urls)] + #[cfg(all(feature = "wallet", target_arch = "wasm32"))] pub mod wallet; #[cfg(all(feature = "wallet", target_arch = "wasm32"))] -pub use wallet::RexieWalletDatabase; +pub use wallet::WalletRexieDatabase; diff --git a/crates/cdk-rexie/src/wallet.rs b/crates/cdk-rexie/src/wallet.rs index 88778f98..70444081 100644 --- a/crates/cdk-rexie/src/wallet.rs +++ b/crates/cdk-rexie/src/wallet.rs @@ -1,3 +1,5 @@ +//! Rexie Browser Database + use std::collections::{HashMap, HashSet}; use std::rc::Rc; use std::result::Result; @@ -28,6 +30,7 @@ const NOSTR_LAST_CHECKED: &str = "nostr_last_check"; const DATABASE_VERSION: u32 = 3; +/// Rexie Database Error #[derive(Debug, Error)] pub enum Error { /// CDK Database Error @@ -39,6 +42,7 @@ pub enum Error { /// Serde Wasm Error #[error(transparent)] SerdeBindgen(#[from] serde_wasm_bindgen::Error), + /// NUT00 Error #[error(transparent)] NUT00(cdk::nuts::nut00::Error), } @@ -52,16 +56,18 @@ impl From for cdk::cdk_database::Error { unsafe impl Send for Error {} unsafe impl Sync for Error {} +/// Wallet Rexie Database #[derive(Debug, Clone)] -pub struct RexieWalletDatabase { +pub struct WalletRexieDatabase { db: Rc>, } // These are okay because we never actually send across threads in the browser -unsafe impl Send for RexieWalletDatabase {} -unsafe impl Sync for RexieWalletDatabase {} +unsafe impl Send for WalletRexieDatabase {} +unsafe impl Sync for WalletRexieDatabase {} -impl RexieWalletDatabase { +impl WalletRexieDatabase { + /// Create new [`WalletRexieDatabase`] pub async fn new() -> Result { let rexie = Rexie::builder("cdk") .version(DATABASE_VERSION) @@ -102,7 +108,7 @@ impl RexieWalletDatabase { #[cfg_attr(target_arch = "wasm32", async_trait(?Send))] #[cfg_attr(not(target_arch = "wasm32"), async_trait)] -impl WalletDatabase for RexieWalletDatabase { +impl WalletDatabase for WalletRexieDatabase { type Err = cdk::cdk_database::Error; async fn add_mint( diff --git a/crates/cdk-sqlite/README.md b/crates/cdk-sqlite/README.md new file mode 100644 index 00000000..c30dc88d --- /dev/null +++ b/crates/cdk-sqlite/README.md @@ -0,0 +1,23 @@ + +# Cashu Development Kit Redb Storage Backend + +**ALPHA** This library is in early development, the api will change and should be used with caution. + +cdk-sqlite is the sqlite storage backend for cdk. + +## Crate Feature Flags + +The following crate feature flags are available: + +| Feature | Default | Description | +|-------------|:-------:|------------------------------------| +| `wallet` | Yes | Enable cashu wallet features | +| `mint` | Yes | Enable cashu mint wallet features | + +## Implemented [NUTs](https://github.com/cashubtc/nuts/): + +See + +## License + +This project is distributed under the MIT software license - see the [LICENSE](../../LICENSE) file for details. diff --git a/crates/cdk-sqlite/src/lib.rs b/crates/cdk-sqlite/src/lib.rs index 48af9629..ea1e51df 100644 --- a/crates/cdk-sqlite/src/lib.rs +++ b/crates/cdk-sqlite/src/lib.rs @@ -1,3 +1,8 @@ +//! SQLite storage backend for cdk + +#![warn(missing_docs)] +#![warn(rustdoc::bare_urls)] + #[cfg(feature = "mint")] pub mod mint; #[cfg(feature = "wallet")] @@ -6,4 +11,4 @@ pub mod wallet; #[cfg(feature = "mint")] pub use mint::MintSqliteDatabase; #[cfg(feature = "wallet")] -pub use wallet::WalletSQLiteDatabase; +pub use wallet::WalletSqliteDatabase; diff --git a/crates/cdk-sqlite/src/mint/error.rs b/crates/cdk-sqlite/src/mint/error.rs index 45d73819..db83ca1a 100644 --- a/crates/cdk-sqlite/src/mint/error.rs +++ b/crates/cdk-sqlite/src/mint/error.rs @@ -1,5 +1,8 @@ +//! SQLite Database Error + use thiserror::Error; +/// SQLite Database Error #[derive(Debug, Error)] pub enum Error { /// SQLX Error diff --git a/crates/cdk-sqlite/src/mint/mod.rs b/crates/cdk-sqlite/src/mint/mod.rs index 11adf7a8..b836938c 100644 --- a/crates/cdk-sqlite/src/mint/mod.rs +++ b/crates/cdk-sqlite/src/mint/mod.rs @@ -21,12 +21,14 @@ use sqlx::{ConnectOptions, Row}; pub mod error; +/// Mint SQLite Database #[derive(Debug, Clone)] pub struct MintSqliteDatabase { pool: SqlitePool, } impl MintSqliteDatabase { + /// Create new [`MintSqliteDatabase`] pub async fn new(path: &Path) -> Result { let path = path.to_str().ok_or(Error::InvalidDbPath)?; let _conn = SqliteConnectOptions::from_str(path)? @@ -42,6 +44,7 @@ impl MintSqliteDatabase { Ok(Self { pool }) } + /// Migrate [`MintSqliteDatabase`] pub async fn migrate(&self) { sqlx::migrate!("./src/mint/migrations") .run(&self.pool) diff --git a/crates/cdk-sqlite/src/wallet/error.rs b/crates/cdk-sqlite/src/wallet/error.rs index fe34cb64..164f3a39 100644 --- a/crates/cdk-sqlite/src/wallet/error.rs +++ b/crates/cdk-sqlite/src/wallet/error.rs @@ -1,5 +1,8 @@ +//! SQLite Wallet Error + use thiserror::Error; +/// SQLite Wallet Error #[derive(Debug, Error)] pub enum Error { /// SQLX Error diff --git a/crates/cdk-sqlite/src/wallet/mod.rs b/crates/cdk-sqlite/src/wallet/mod.rs index fc012122..b61dd7e3 100644 --- a/crates/cdk-sqlite/src/wallet/mod.rs +++ b/crates/cdk-sqlite/src/wallet/mod.rs @@ -20,12 +20,14 @@ use sqlx::{ConnectOptions, Row}; pub mod error; +/// Wallet SQLite Database #[derive(Debug, Clone)] -pub struct WalletSQLiteDatabase { +pub struct WalletSqliteDatabase { pool: SqlitePool, } -impl WalletSQLiteDatabase { +impl WalletSqliteDatabase { + /// Create new [`WalletSqliteDatabase`] pub async fn new(path: &Path) -> Result { let path = path.to_str().ok_or(Error::InvalidDbPath)?; let _conn = SqliteConnectOptions::from_str(path)? @@ -41,6 +43,7 @@ impl WalletSQLiteDatabase { Ok(Self { pool }) } + /// Migrate [`WalletSqliteDatabase`] pub async fn migrate(&self) { sqlx::migrate!("./src/wallet/migrations") .run(&self.pool) @@ -50,7 +53,7 @@ impl WalletSQLiteDatabase { } #[async_trait] -impl WalletDatabase for WalletSQLiteDatabase { +impl WalletDatabase for WalletSqliteDatabase { type Err = cdk_database::Error; async fn add_mint( diff --git a/crates/cdk/README.md b/crates/cdk/README.md index 114c3ddc..036beefe 100644 --- a/crates/cdk/README.md +++ b/crates/cdk/README.md @@ -1,19 +1,23 @@ # Cashu Development Kit +**ALPHA** This library is in early development, the api will change and should be used with caution. + +CDK is the core crate implementing the cashu protocol for both the Wallet and Mint. + +## Crate Feature Flags + +The following crate feature flags are available: + +| Feature | Default | Description | +|-------------|:-------:|------------------------------------| +| `wallet` | Yes | Enable cashu wallet features | +| `mint` | Yes | Enable cashu mint wallet features | + ## Implemented [NUTs](https://github.com/cashubtc/nuts/): -- :heavy_check_mark: [NUT-00](https://github.com/cashubtc/nuts/blob/main/00.md) -- :heavy_check_mark: [NUT-01](https://github.com/cashubtc/nuts/blob/main/01.md) -- :heavy_check_mark: [NUT-02](https://github.com/cashubtc/nuts/blob/main/02.md) -- :heavy_check_mark: [NUT-03](https://github.com/cashubtc/nuts/blob/main/03.md) -- :heavy_check_mark: [NUT-04](https://github.com/cashubtc/nuts/blob/main/04.md) -- :heavy_check_mark: [NUT-05](https://github.com/cashubtc/nuts/blob/main/05.md) -- :heavy_check_mark: [NUT-06](https://github.com/cashubtc/nuts/blob/main/06.md) -- :heavy_check_mark: [NUT-07](https://github.com/cashubtc/nuts/blob/main/07.md) -- :heavy_check_mark: [NUT-08](https://github.com/cashubtc/nuts/blob/main/08.md) -- :heavy_check_mark: [NUT-09](https://github.com/cashubtc/nuts/blob/main/09.md) -- :heavy_check_mark: [NUT-10](https://github.com/cashubtc/nuts/blob/main/10.md) -- :heavy_check_mark: [NUT-11](https://github.com/cashubtc/nuts/blob/main/11.md) -- :heavy_check_mark: [NUT-12](https://github.com/cashubtc/nuts/blob/main/12.md) -- :heavy_check_mark: [NUT-13](https://github.com/cashubtc/nuts/blob/main/13.md) +See + +## License + +This project is distributed under the MIT software license - see the [LICENSE](../../LICENSE) file for details diff --git a/crates/cdk/src/amount.rs b/crates/cdk/src/amount.rs index 19e69c97..5877fc24 100644 --- a/crates/cdk/src/amount.rs +++ b/crates/cdk/src/amount.rs @@ -1,3 +1,7 @@ +//! CDK Amount +//! +//! Is any and will be treated as the unit of the wallet + use std::fmt; use serde::{Deserialize, Serialize}; @@ -8,6 +12,7 @@ use serde::{Deserialize, Serialize}; pub struct Amount(u64); impl Amount { + /// Amount zero pub const ZERO: Amount = Amount(0); /// Split into parts that are powers of two diff --git a/crates/cdk/src/cdk_database/mint_memory.rs b/crates/cdk/src/cdk_database/mint_memory.rs index 0efcfa93..f636cb5c 100644 --- a/crates/cdk/src/cdk_database/mint_memory.rs +++ b/crates/cdk/src/cdk_database/mint_memory.rs @@ -1,3 +1,5 @@ +//! Mint in memory database + use std::collections::HashMap; use std::sync::Arc; @@ -13,6 +15,7 @@ use crate::nuts::{ use crate::secret::Secret; use crate::types::{MeltQuote, MintQuote}; +/// Mint Memory Database #[derive(Debug, Clone)] pub struct MintMemoryDatabase { active_keysets: Arc>>, @@ -25,6 +28,7 @@ pub struct MintMemoryDatabase { } impl MintMemoryDatabase { + /// Create new [`MintMemoryDatabase`] #[allow(clippy::too_many_arguments)] pub fn new( active_keysets: HashMap, diff --git a/crates/cdk/src/cdk_database/mod.rs b/crates/cdk/src/cdk_database/mod.rs index 6c933dfe..8fee0500 100644 --- a/crates/cdk/src/cdk_database/mod.rs +++ b/crates/cdk/src/cdk_database/mod.rs @@ -35,63 +35,90 @@ pub mod wallet_memory; #[cfg(feature = "wallet")] pub use wallet_memory::WalletMemoryDatabase; +/// CDK_database error #[derive(Debug, Error)] pub enum Error { + /// Database Error #[error(transparent)] Database(Box), + /// CDK Error #[error(transparent)] Cdk(#[from] crate::error::Error), + /// NUT01 Error #[error(transparent)] NUT01(#[from] crate::nuts::nut00::Error), + /// Unknown Quote #[error("Unknown Quote")] UnknownQuote, } +/// Wallet Database trait #[cfg(feature = "wallet")] #[cfg_attr(target_arch = "wasm32", async_trait(?Send))] #[cfg_attr(not(target_arch = "wasm32"), async_trait)] pub trait WalletDatabase: Debug { + /// Wallet Database Error type Err: Into + From; + /// Add Mint to storage async fn add_mint( &self, mint_url: UncheckedUrl, mint_info: Option, ) -> Result<(), Self::Err>; + /// Remove Mint from storage async fn remove_mint(&self, mint_url: UncheckedUrl) -> Result<(), Self::Err>; + /// Get mint from storage async fn get_mint(&self, mint_url: UncheckedUrl) -> Result, Self::Err>; + /// Get all mints from storage async fn get_mints(&self) -> Result>, Self::Err>; + /// Update mint url async fn update_mint_url( &self, old_mint_url: UncheckedUrl, new_mint_url: UncheckedUrl, ) -> Result<(), Self::Err>; + /// Add mint keyset to storage async fn add_mint_keysets( &self, mint_url: UncheckedUrl, keysets: Vec, ) -> Result<(), Self::Err>; + /// Get mint keysets for mint url async fn get_mint_keysets( &self, mint_url: UncheckedUrl, ) -> Result>, Self::Err>; + /// Get mint keyset by id async fn get_keyset_by_id(&self, keyset_id: &Id) -> Result, Self::Err>; + /// Add mint quote to storage async fn add_mint_quote(&self, quote: MintQuote) -> Result<(), Self::Err>; + /// Get mint quote from storage async fn get_mint_quote(&self, quote_id: &str) -> Result, Self::Err>; + /// Get mint quotes from storage async fn get_mint_quotes(&self) -> Result, Self::Err>; + /// Remove mint quote from storage async fn remove_mint_quote(&self, quote_id: &str) -> Result<(), Self::Err>; + /// Add melt quote to storage async fn add_melt_quote(&self, quote: MeltQuote) -> Result<(), Self::Err>; + /// Get melt quote from storage async fn get_melt_quote(&self, quote_id: &str) -> Result, Self::Err>; + /// Remove melt quote from storage async fn remove_melt_quote(&self, quote_id: &str) -> Result<(), Self::Err>; + /// Add [`Keys`] to storage async fn add_keys(&self, keys: Keys) -> Result<(), Self::Err>; + /// Get [`Keys`] from storage async fn get_keys(&self, id: &Id) -> Result, Self::Err>; + /// Remove [`Keys`] from storage async fn remove_keys(&self, id: &Id) -> Result<(), Self::Err>; + /// Add [`Proofs`] to storage async fn add_proofs(&self, proof_info: Vec) -> Result<(), Self::Err>; + /// Get proofs from storage async fn get_proofs( &self, mint_url: Option, @@ -99,17 +126,23 @@ pub trait WalletDatabase: Debug { state: Option>, spending_conditions: Option>, ) -> Result>, Self::Err>; + /// Remove proofs from storage async fn remove_proofs(&self, proofs: &Proofs) -> Result<(), Self::Err>; + /// Set Proof state async fn set_proof_state(&self, y: PublicKey, state: State) -> Result<(), Self::Err>; + /// Increment Keyset counter async fn increment_keyset_counter(&self, keyset_id: &Id, count: u32) -> Result<(), Self::Err>; + /// Get current Keyset counter async fn get_keyset_counter(&self, keyset_id: &Id) -> Result, Self::Err>; + /// Get when nostr key was last checked async fn get_nostr_last_checked( &self, verifying_key: &PublicKey, ) -> Result, Self::Err>; + /// Update last checked time async fn add_nostr_last_checked( &self, verifying_key: PublicKey, @@ -117,60 +150,88 @@ pub trait WalletDatabase: Debug { ) -> Result<(), Self::Err>; } +/// Mint Database trait #[cfg(feature = "mint")] #[async_trait] pub trait MintDatabase { + /// Mint Database Error type Err: Into + From; + /// Add Active Keyset async fn add_active_keyset(&self, unit: CurrencyUnit, id: Id) -> Result<(), Self::Err>; + /// Get Active Keyset async fn get_active_keyset_id(&self, unit: &CurrencyUnit) -> Result, Self::Err>; + /// Get all Active Keyset async fn get_active_keysets(&self) -> Result, Self::Err>; + /// Add [`MintQuote`] async fn add_mint_quote(&self, quote: MintQuote) -> Result<(), Self::Err>; + /// Get [`MintQuote`] async fn get_mint_quote(&self, quote_id: &str) -> Result, Self::Err>; + /// Update state of [`MintQuote`] async fn update_mint_quote_state( &self, quote_id: &str, state: MintQuoteState, ) -> Result; + /// Get all [`MintQuote`]s async fn get_mint_quotes(&self) -> Result, Self::Err>; + /// Remove [`MintQuote`] async fn remove_mint_quote(&self, quote_id: &str) -> Result<(), Self::Err>; + /// Add [`MeltQuote`] async fn add_melt_quote(&self, quote: MeltQuote) -> Result<(), Self::Err>; + /// Get [`MeltQuote`] async fn get_melt_quote(&self, quote_id: &str) -> Result, Self::Err>; + /// Update [`MeltQuote`] state async fn update_melt_quote_state( &self, quote_id: &str, state: MeltQuoteState, ) -> Result; + /// Get all [`MeltQuote`]s async fn get_melt_quotes(&self) -> Result, Self::Err>; + /// Remove [`MeltQuote`] async fn remove_melt_quote(&self, quote_id: &str) -> Result<(), Self::Err>; + /// Add [`MintKeySetInfo`] async fn add_keyset_info(&self, keyset: MintKeySetInfo) -> Result<(), Self::Err>; + /// Get [`MintKeySetInfo`] async fn get_keyset_info(&self, id: &Id) -> Result, Self::Err>; + /// Get [`MintKeySetInfo`]s async fn get_keyset_infos(&self) -> Result, Self::Err>; + /// Add spent [`Proofs`] async fn add_spent_proofs(&self, proof: Proofs) -> Result<(), Self::Err>; + /// Get spent [`Proof`] by secret async fn get_spent_proof_by_secret(&self, secret: &Secret) -> Result, Self::Err>; + /// Get spent [`Proof`] by y async fn get_spent_proof_by_y(&self, y: &PublicKey) -> Result, Self::Err>; + /// Add pending [`Proofs`] async fn add_pending_proofs(&self, proof: Proofs) -> Result<(), Self::Err>; + /// Get pending [`Proof`] by secret async fn get_pending_proof_by_secret( &self, secret: &Secret, ) -> Result, Self::Err>; + /// Get pending [`Proof`] by y async fn get_pending_proof_by_y(&self, y: &PublicKey) -> Result, Self::Err>; + /// Remove pending [`Proofs`] async fn remove_pending_proofs(&self, secret: Vec<&Secret>) -> Result<(), Self::Err>; + /// Add [`BlindSignature`] async fn add_blinded_signature( &self, blinded_message: PublicKey, blinded_signature: BlindSignature, ) -> Result<(), Self::Err>; + /// Get [`BlindSignature`] async fn get_blinded_signature( &self, blinded_message: &PublicKey, ) -> Result, Self::Err>; + /// Get [`BlindSignature`]s async fn get_blinded_signatures( &self, blinded_messages: Vec, diff --git a/crates/cdk/src/cdk_database/wallet_memory.rs b/crates/cdk/src/cdk_database/wallet_memory.rs index b80bf914..c837e15f 100644 --- a/crates/cdk/src/cdk_database/wallet_memory.rs +++ b/crates/cdk/src/cdk_database/wallet_memory.rs @@ -1,4 +1,4 @@ -//! Memory Database +//! Wallet in memory database use std::collections::{HashMap, HashSet}; use std::sync::Arc; @@ -15,6 +15,7 @@ use crate::types::{MeltQuote, MintQuote, ProofInfo}; use crate::url::UncheckedUrl; use crate::util::unix_time; +/// Wallet in Memory Database #[derive(Default, Debug, Clone)] pub struct WalletMemoryDatabase { mints: Arc>>>, @@ -29,6 +30,7 @@ pub struct WalletMemoryDatabase { } impl WalletMemoryDatabase { + /// Create new [`WalletMemoryDatabase`] pub fn new( mint_quotes: Vec, melt_quotes: Vec, diff --git a/crates/cdk/src/dhke.rs b/crates/cdk/src/dhke.rs index 88dbaec6..2b16ca35 100644 --- a/crates/cdk/src/dhke.rs +++ b/crates/cdk/src/dhke.rs @@ -18,6 +18,9 @@ use crate::SECP256K1; const DOMAIN_SEPARATOR: &[u8; 28] = b"Secp256k1_HashToCurve_Cashu_"; +/// Deterministically maps a message to a public key point on the secp256k1 curve, utilizing a domain separator to ensure uniqueness. +/// +/// For definationn in NUT see [NUT-00](https://github.com/cashubtc/nuts/blob/main/00.md) pub fn hash_to_curve(message: &[u8]) -> Result { let msg_to_hash: Vec = [DOMAIN_SEPARATOR, message].concat(); @@ -44,6 +47,7 @@ pub fn hash_to_curve(message: &[u8]) -> Result { Err(Error::NoValidPoint) } +/// Convert iterator of [`PublicKey`] to byte array pub fn hash_e(public_keys: I) -> [u8; 32] where I: IntoIterator, @@ -158,7 +162,7 @@ pub fn verify_message( #[cfg(test)] mod tests { - use core::str::FromStr; + use std::str::FromStr; use super::*; diff --git a/crates/cdk/src/error.rs b/crates/cdk/src/error.rs index 9fa09174..8e2ac419 100644 --- a/crates/cdk/src/error.rs +++ b/crates/cdk/src/error.rs @@ -9,6 +9,7 @@ use thiserror::Error; use crate::util::hex; +/// CDK Error #[derive(Debug, Error)] pub enum Error { /// Mint does not have a key for amount @@ -77,10 +78,16 @@ pub enum Error { CustomError(String), } +/// CDK Error Response +/// +/// See NUT defenation in [00](https://github.com/cashubtc/nuts/blob/main/00.md) #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct ErrorResponse { + /// Error Code pub code: ErrorCode, + /// Human readable Text pub error: Option, + /// Longer human readable description pub detail: Option, } @@ -97,12 +104,14 @@ impl fmt::Display for ErrorResponse { } impl ErrorResponse { + /// Error response from json pub fn from_json(json: &str) -> Result { let value: Value = serde_json::from_str(json)?; Self::from_value(value) } + /// Error response from json Value pub fn from_value(value: Value) -> Result { match serde_json::from_value::(value.clone()) { Ok(res) => Ok(res), @@ -115,11 +124,16 @@ impl ErrorResponse { } } +/// Possible Error Codes #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] pub enum ErrorCode { + /// Token is already spent TokenAlreadySpent, + /// Quote is not paid QuoteNotPaid, + /// Keyset is not found KeysetNotFound, + /// Unknown error code Unknown(u16), } diff --git a/crates/cdk/src/lib.rs b/crates/cdk/src/lib.rs index d17dfd3d..c7dc79c6 100644 --- a/crates/cdk/src/lib.rs +++ b/crates/cdk/src/lib.rs @@ -1,8 +1,7 @@ -extern crate core; +//! Rust implementation of the Cashu Protocol -pub use bitcoin::hashes::sha256::Hash as Sha256; -pub use bitcoin::secp256k1; -pub use lightning_invoice::{self, Bolt11Invoice}; +#![warn(missing_docs)] +#![warn(rustdoc::bare_urls)] pub mod amount; pub mod cdk_database; @@ -18,15 +17,27 @@ pub mod util; #[cfg(feature = "wallet")] pub mod wallet; +#[doc(hidden)] +pub use bitcoin::secp256k1; +#[doc(hidden)] +pub use lightning_invoice::{self, Bolt11Invoice}; #[cfg(feature = "mint")] +#[doc(hidden)] pub use mint::Mint; #[cfg(feature = "wallet")] +#[doc(hidden)] pub use wallet::Wallet; +#[doc(hidden)] pub use self::amount::Amount; +#[doc(hidden)] pub use self::url::UncheckedUrl; +#[doc(hidden)] pub use self::util::SECP256K1; #[cfg(feature = "wallet")] +#[doc(hidden)] pub use self::wallet::client::HttpClient; +/// Result +#[doc(hidden)] pub type Result> = std::result::Result; diff --git a/crates/cdk/src/mint/error.rs b/crates/cdk/src/mint/error.rs index 17f0b219..6e713fdf 100644 --- a/crates/cdk/src/mint/error.rs +++ b/crates/cdk/src/mint/error.rs @@ -6,6 +6,7 @@ use thiserror::Error; use crate::cdk_database; use crate::error::{ErrorCode, ErrorResponse}; +/// CDK Mint Error #[derive(Debug, Error)] pub enum Error { /// Unknown Keyset @@ -14,47 +15,67 @@ pub enum Error { /// Inactive Keyset #[error("Inactive Keyset")] InactiveKeyset, + /// There is not key for amount given #[error("No key for amount")] AmountKey, + /// Amount is not what is expected #[error("Amount")] Amount, + /// Duplicate proofs provided #[error("Duplicate proofs")] DuplicateProofs, + /// Token is already spent #[error("Token Already Spent")] TokenAlreadySpent, + /// Token is already pending #[error("Token Pending")] TokenPending, + /// Quote is not paiud #[error("Quote not paid")] UnpaidQuote, + /// Quote has already been paid #[error("Quote is already paid")] PaidQuote, + /// Quote is not known #[error("Unknown quote")] UnknownQuote, + /// Quote is pending #[error("Quote pending")] PendingQuote, + /// ecash already issued for quote #[error("Quote already issued")] IssuedQuote, + /// Unknown secret kind #[error("Unknown secret kind")] UnknownSecretKind, + /// Multiple units provided #[error("Cannot have multiple units")] MultipleUnits, + /// BlindMessage is already signed #[error("Blinded Message is already signed")] BlindedMessageAlreadySigned, + /// Cashu Error #[error(transparent)] Cashu(#[from] crate::error::Error), + /// Secret Error #[error(transparent)] Secret(#[from] crate::secret::Error), + /// NUT00 Error #[error(transparent)] NUT00(#[from] crate::nuts::nut00::Error), + /// NUT11 Error #[error(transparent)] NUT11(#[from] crate::nuts::nut11::Error), + /// NUT12 Error #[error(transparent)] Nut12(#[from] crate::nuts::nut12::Error), + /// NUT14 Error #[error(transparent)] Nut14(#[from] crate::nuts::nut14::Error), /// Database Error #[error(transparent)] Database(#[from] cdk_database::Error), + /// Custom Error #[error("`{0}`")] Custom(String), } diff --git a/crates/cdk/src/mint/mod.rs b/crates/cdk/src/mint/mod.rs index 7145d583..940b07aa 100644 --- a/crates/cdk/src/mint/mod.rs +++ b/crates/cdk/src/mint/mod.rs @@ -21,18 +21,23 @@ use crate::Amount; pub mod error; +/// Cashu Mint #[derive(Clone)] pub struct Mint { + /// Mint Url pub mint_url: UncheckedUrl, mint_info: MintInfo, keysets: Arc>>, secp_ctx: Secp256k1, xpriv: ExtendedPrivKey, + /// Mint Expected [`FeeReserve`] pub fee_reserve: FeeReserve, + /// Mint Storage backend pub localstore: Arc + Send + Sync>, } impl Mint { + /// Create new [`Mint`] pub async fn new( mint_url: &str, seed: &[u8], @@ -844,19 +849,30 @@ impl Mint { /// Mint Fee Reserve #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct FeeReserve { + /// Absolute expected min fee pub min_fee_reserve: Amount, + /// Percentage expected fee pub percent_fee_reserve: f32, } /// Mint Keyset Info #[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)] pub struct MintKeySetInfo { + /// Keyset [`Id`] pub id: Id, + /// Keyset [`CurrencyUnit`] pub unit: CurrencyUnit, + /// Keyset active or inactive + /// Mint will only issue new [`BlindSignature`] on active keysets pub active: bool, + /// Starting unix time Keyset is valid from pub valid_from: u64, + /// When the Keyset is valid to + /// This is not shown to the wallet and can only be used internally pub valid_to: Option, + /// [`DerivationPath`] of Keyset pub derivation_path: DerivationPath, + /// Max order of keyset pub max_order: u8, } diff --git a/crates/cdk/src/nuts/mod.rs b/crates/cdk/src/nuts/mod.rs index ea9c61c8..0e496619 100644 --- a/crates/cdk/src/nuts/mod.rs +++ b/crates/cdk/src/nuts/mod.rs @@ -1,3 +1,7 @@ +//! Nuts +//! +//! See all at + pub mod nut00; pub mod nut01; pub mod nut02; diff --git a/crates/cdk/src/nuts/nut00.rs b/crates/cdk/src/nuts/nut00.rs index 61a3ec7f..98bc9231 100644 --- a/crates/cdk/src/nuts/nut00.rs +++ b/crates/cdk/src/nuts/nut00.rs @@ -30,6 +30,7 @@ use crate::Amount; /// List of [Proof] pub type Proofs = Vec; +/// NUT00 Error #[derive(Debug, Error)] pub enum Error { /// Proofs required @@ -141,6 +142,7 @@ pub enum Witness { } impl Witness { + /// Add signatures to [`Witness`] pub fn add_signatures(&mut self, signatues: Vec) { match self { Self::P2PKWitness(p2pk_witness) => p2pk_witness.signatures.extend(signatues), @@ -154,6 +156,7 @@ impl Witness { } } + /// Get signatures on [`Witness`] pub fn signatures(&self) -> Option> { match self { Self::P2PKWitness(witness) => Some(witness.signatures.clone()), @@ -161,6 +164,7 @@ impl Witness { } } + /// Get preimage from [`Witness`] pub fn preimage(&self) -> Option { match self { Self::P2PKWitness(_witness) => None, @@ -191,6 +195,7 @@ pub struct Proof { } impl Proof { + /// Create new [`Proof`] pub fn new(amount: Amount, keyset_id: Id, secret: Secret, c: PublicKey) -> Self { Proof { amount, @@ -202,6 +207,9 @@ impl Proof { } } + /// Get y from proof + /// + /// Where y is `hash_to_curve(secret)` pub fn y(&self) -> Result { Ok(hash_to_curve(self.secret.as_bytes())?) } @@ -225,12 +233,17 @@ impl PartialOrd for Proof { } } +/// Currency Unit #[derive(Debug, Clone, Default, PartialEq, Eq, Hash)] pub enum CurrencyUnit { + /// Sat #[default] Sat, + /// Msat Msat, + /// Usd Usd, + /// Custom unit Custom(String), } @@ -278,10 +291,13 @@ impl<'de> Deserialize<'de> for CurrencyUnit { } } +/// Payment Method #[derive(Debug, Clone, Default, PartialEq, Eq, Hash)] pub enum PaymentMethod { + /// Bolt11 payment type #[default] Bolt11, + /// Custom payment type: Custom(String), } @@ -325,6 +341,7 @@ impl<'de> Deserialize<'de> for PaymentMethod { } } +/// PreMint #[derive(Debug, Clone, PartialEq, Eq, Serialize)] pub struct PreMint { /// Blinded message @@ -349,8 +366,10 @@ impl PartialOrd for PreMint { } } +/// Premint Secrets #[derive(Debug, Default, Clone, PartialEq, Eq, Serialize)] pub struct PreMintSecrets { + /// Secrets pub secrets: Vec, } @@ -382,6 +401,7 @@ impl PreMintSecrets { Ok(PreMintSecrets { secrets: output }) } + /// Outputs from pre defined secrets pub fn from_secrets( keyset_id: Id, amounts: Vec, @@ -428,6 +448,7 @@ impl PreMintSecrets { Ok(PreMintSecrets { secrets: output }) } + /// Outputs with specific spending conditions pub fn with_conditions( keyset_id: Id, amount: Amount, @@ -457,18 +478,25 @@ impl PreMintSecrets { Ok(PreMintSecrets { secrets: output }) } + /// Iterate over secrets + #[inline] pub fn iter(&self) -> impl Iterator { self.secrets.iter() } + /// Length of secrets + #[inline] pub fn len(&self) -> usize { self.secrets.len() } + /// If secrets is empty + #[inline] pub fn is_empty(&self) -> bool { self.secrets.is_empty() } + /// Totoal amount of secrets pub fn total_amount(&self) -> Amount { self.secrets .iter() @@ -476,26 +504,38 @@ impl PreMintSecrets { .sum() } + /// [`BlindedMessage`]s from [`PreMintSecrets`] + #[inline] pub fn blinded_messages(&self) -> Vec { self.iter().map(|pm| pm.blinded_message.clone()).collect() } + /// [`Secret`]s from [`PreMintSecrets`] + #[inline] pub fn secrets(&self) -> Vec { self.iter().map(|pm| pm.secret.clone()).collect() } + /// Blinding factor from [`PreMintSecrets`] + #[inline] pub fn rs(&self) -> Vec { self.iter().map(|pm| pm.r.clone()).collect() } + /// Amounts from [`PreMintSecrets`] + #[inline] pub fn amounts(&self) -> Vec { self.iter().map(|pm| pm.amount).collect() } + /// Combine [`PreMintSecrets`] + #[inline] pub fn combine(&mut self, mut other: Self) { self.secrets.append(&mut other.secrets) } + /// Sort [`PreMintSecrets`] by [`Amount`] + #[inline] pub fn sort_secrets(&mut self) { self.secrets.sort(); } @@ -523,8 +563,10 @@ impl PartialOrd for PreMintSecrets { } } +/// Token #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct Token { + /// Proofs in [`Token`] by mint pub token: Vec, /// Memo for token #[serde(skip_serializing_if = "Option::is_none")] @@ -535,6 +577,7 @@ pub struct Token { } impl Token { + /// Create new [`Token`] pub fn new( mint_url: UncheckedUrl, proofs: Proofs, @@ -555,6 +598,8 @@ impl Token { }) } + /// Token Info + /// Assumes only one mint in [`Token`] pub fn token_info(&self) -> (Amount, String) { let mut amount = Amount::ZERO; @@ -595,13 +640,17 @@ impl fmt::Display for Token { } } +/// Mint Proofs #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct MintProofs { + /// Url of mint pub mint: UncheckedUrl, + /// [`Proofs`] pub proofs: Proofs, } impl MintProofs { + /// Create new [`MintProofs`] pub fn new(mint_url: UncheckedUrl, proofs: Proofs) -> Self { Self { mint: mint_url, diff --git a/crates/cdk/src/nuts/nut01/mod.rs b/crates/cdk/src/nuts/nut01/mod.rs index f8e3ad6d..ed4c80e4 100644 --- a/crates/cdk/src/nuts/nut01/mod.rs +++ b/crates/cdk/src/nuts/nut01/mod.rs @@ -18,17 +18,23 @@ pub use self::secret_key::SecretKey; use super::nut02::KeySet; use crate::amount::Amount; +/// Nut01 Error #[derive(Debug, Error)] pub enum Error { + /// Secp256k1 Error #[error(transparent)] Secp256k1(#[from] secp256k1::Error), + /// Json Error #[error(transparent)] Json(#[from] serde_json::Error), - #[cfg(feature = "nostr")] - #[error(transparent)] - NostrKey(#[from] nostr_sdk::key::Error), + /// Invalid Pubkey size #[error("Invalid public key size: expected={expected}, found={found}")] - InvalidPublicKeySize { expected: usize, found: usize }, + InvalidPublicKeySize { + /// Expected size + expected: usize, + /// Actual size + found: usize, + }, } /// Mint Keys [NUT-01] @@ -47,16 +53,19 @@ impl From for Keys { } impl Keys { + /// Create new [`Keys`] #[inline] pub fn new(keys: BTreeMap) -> Self { Self(keys) } + /// Get [`Keys`] #[inline] pub fn keys(&self) -> &BTreeMap { &self.0 } + /// Get [`PublicKey`] for [`Amount`] #[inline] pub fn amount_key(&self, amount: Amount) -> Option { self.0.get(&amount.to_string()).copied() @@ -72,6 +81,7 @@ impl Keys { /// Mint Public Keys [NUT-01] #[derive(Debug, Clone, PartialEq, Eq, Serialize)] pub struct KeysResponse { + /// Keysets pub keysets: Vec, } @@ -116,19 +126,24 @@ impl DerefMut for MintKeys { } impl MintKeys { + /// Create new [`MintKeys`] #[inline] pub fn new(map: BTreeMap) -> Self { Self(map) } } +/// Mint Public Private key pair #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct MintKeyPair { + /// Publickey pub public_key: PublicKey, + /// Secretkey pub secret_key: SecretKey, } impl MintKeyPair { + /// [`MintKeyPair`] from secret key #[inline] pub fn from_secret_key(secret_key: SecretKey) -> Self { Self { diff --git a/crates/cdk/src/nuts/nut01/public_key.rs b/crates/cdk/src/nuts/nut01/public_key.rs index 8615aca5..5f61e9e7 100644 --- a/crates/cdk/src/nuts/nut01/public_key.rs +++ b/crates/cdk/src/nuts/nut01/public_key.rs @@ -11,6 +11,7 @@ use serde::{Deserialize, Deserializer, Serialize}; use super::Error; use crate::SECP256K1; +/// PublicKey #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct PublicKey { inner: secp256k1::PublicKey, @@ -30,38 +31,6 @@ impl From for PublicKey { } } -#[cfg(feature = "nostr")] -impl TryFrom for nostr_sdk::PublicKey { - type Error = Error; - fn try_from(pubkey: PublicKey) -> Result { - Ok(nostr_sdk::PublicKey::from_slice(&pubkey.to_bytes())?) - } -} - -#[cfg(feature = "nostr")] -impl TryFrom<&PublicKey> for nostr_sdk::PublicKey { - type Error = Error; - fn try_from(pubkey: &PublicKey) -> Result { - Ok(nostr_sdk::PublicKey::from_slice(&pubkey.to_bytes())?) - } -} - -#[cfg(feature = "nostr")] -impl TryFrom for PublicKey { - type Error = Error; - fn try_from(pubkey: nostr_sdk::PublicKey) -> Result { - (&pubkey).try_into() - } -} - -#[cfg(feature = "nostr")] -impl TryFrom<&nostr_sdk::PublicKey> for PublicKey { - type Error = Error; - fn try_from(pubkey: &nostr_sdk::PublicKey) -> Result { - PublicKey::from_slice(&pubkey.to_bytes()) - } -} - impl PublicKey { /// Parse from `bytes` #[inline] @@ -92,16 +61,19 @@ impl PublicKey { }) } + /// [`PublicKey`] to bytes #[inline] pub fn to_bytes(&self) -> [u8; 33] { self.inner.serialize() } + /// To uncompressed bytes #[inline] pub fn to_uncompressed_bytes(&self) -> [u8; 65] { self.inner.serialize_uncompressed() } + /// To [`XOnlyPublicKey`] #[inline] pub fn x_only_public_key(&self) -> XOnlyPublicKey { self.inner.x_only_public_key().0 diff --git a/crates/cdk/src/nuts/nut01/secret_key.rs b/crates/cdk/src/nuts/nut01/secret_key.rs index b5474653..68c72516 100644 --- a/crates/cdk/src/nuts/nut01/secret_key.rs +++ b/crates/cdk/src/nuts/nut01/secret_key.rs @@ -13,6 +13,7 @@ use serde::{Deserialize, Deserializer, Serialize}; use super::{Error, PublicKey}; use crate::SECP256K1; +/// SecretKey #[derive(Debug, Clone, PartialEq, Eq)] pub struct SecretKey { inner: secp256k1::SecretKey, @@ -32,22 +33,6 @@ impl From for SecretKey { } } -#[cfg(feature = "nostr")] -impl TryFrom for nostr_sdk::SecretKey { - type Error = Error; - fn try_from(seckey: SecretKey) -> Result { - (&seckey).try_into() - } -} - -#[cfg(feature = "nostr")] -impl TryFrom<&SecretKey> for nostr_sdk::SecretKey { - type Error = Error; - fn try_from(seckey: &SecretKey) -> Result { - Ok(nostr_sdk::SecretKey::from_slice(&seckey.to_secret_bytes())?) - } -} - impl fmt::Display for SecretKey { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.to_secret_hex()) @@ -105,11 +90,13 @@ impl SecretKey { self.inner.public_key(&SECP256K1).into() } + /// [`SecretKey`] to [`Scalar`] #[inline] pub fn to_scalar(self) -> Scalar { Scalar::from(self.inner) } + /// [`SecretKey`] as [`Scalar`] #[inline] pub fn as_scalar(&self) -> Scalar { Scalar::from(self.inner) diff --git a/crates/cdk/src/nuts/nut02.rs b/crates/cdk/src/nuts/nut02.rs index 95ce2866..6824346e 100644 --- a/crates/cdk/src/nuts/nut02.rs +++ b/crates/cdk/src/nuts/nut02.rs @@ -30,30 +30,39 @@ use crate::util::hex; #[cfg(feature = "mint")] use crate::Amount; +/// NUT02 Error #[derive(Debug, Error)] pub enum Error { + /// Hex Error #[error(transparent)] HexError(#[from] hex::Error), + /// Keyset length error #[error("NUT02: ID length invalid")] Length, + /// Unknown version #[error("NUT02: Unknown Version")] UnknownVersion, + /// Slice Error #[error(transparent)] Slice(#[from] TryFromSliceError), } +/// Keyset version #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum KeySetVersion { + /// Current Version 00 Version00, } impl KeySetVersion { + /// [`KeySetVersion`] to byte pub fn to_byte(&self) -> u8 { match self { Self::Version00 => 0, } } + /// [`KeySetVersion`] from byte pub fn from_byte(byte: &u8) -> Result { match byte { 0 => Ok(Self::Version00), @@ -84,10 +93,12 @@ impl Id { const STRLEN: usize = 14; const BYTELEN: usize = 7; + /// [`Id`] to bytes pub fn to_bytes(&self) -> Vec { [vec![self.version.to_byte()], self.id.to_vec()].concat() } + /// [`Id`] from bytes pub fn from_bytes(bytes: &[u8]) -> Result { Ok(Self { version: KeySetVersion::from_byte(&bytes[0])?, @@ -220,6 +231,7 @@ pub struct KeysetResponse { } impl KeysetResponse { + /// Create new [`KeysetResponse`] pub fn new(keysets: Vec) -> Self { Self { keysets: keysets.into_iter().map(|keyset| keyset.into()).collect(), @@ -227,10 +239,14 @@ impl KeysetResponse { } } +/// Keyset #[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)] pub struct KeySet { + /// Keyset [`Id`] pub id: Id, + /// Keyset [`CurrencyUnit`] pub unit: CurrencyUnit, + /// Keyset [`Keys`] pub keys: Keys, } @@ -245,10 +261,15 @@ impl From for KeySet { } } +/// KeySetInfo #[derive(Debug, Clone, Hash, PartialEq, Eq, Deserialize, Serialize)] pub struct KeySetInfo { + /// Keyset [`Id`] pub id: Id, + /// Keyset [`CurrencyUnit`] pub unit: CurrencyUnit, + /// Keyset state + /// Mint will only sign from an active keyset pub active: bool, } @@ -262,16 +283,21 @@ impl From for KeySetInfo { } } +/// MintKeyset #[cfg(feature = "mint")] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct MintKeySet { + /// Keyset [`Id`] pub id: Id, + /// Keyset [`CurrencyUnit`] pub unit: CurrencyUnit, + /// Keyset [`MintKeys`] pub keys: MintKeys, } #[cfg(feature = "mint")] impl MintKeySet { + /// Generate new [`MintKeySet`] pub fn generate( secp: &Secp256k1, xpriv: ExtendedPrivKey, @@ -306,6 +332,7 @@ impl MintKeySet { } } + /// Generate new [`MintKeySet`] from seed pub fn generate_from_seed( secp: &Secp256k1, seed: &[u8], @@ -325,6 +352,7 @@ impl MintKeySet { ) } + /// Generate new [`MintKeySet`] from xpriv pub fn generate_from_xpriv( secp: &Secp256k1, xpriv: ExtendedPrivKey, diff --git a/crates/cdk/src/nuts/nut03.rs b/crates/cdk/src/nuts/nut03.rs index d083d3cf..fcc6bcba 100644 --- a/crates/cdk/src/nuts/nut03.rs +++ b/crates/cdk/src/nuts/nut03.rs @@ -7,9 +7,12 @@ use serde::{Deserialize, Serialize}; use super::nut00::{BlindSignature, BlindedMessage, PreMintSecrets, Proofs}; use crate::Amount; +/// Preswap information #[derive(Debug, Clone, PartialEq, Eq, Serialize)] pub struct PreSwap { + /// Preswap mint secrets pub pre_mint_secrets: PreMintSecrets, + /// Swap request pub swap_request: SwapRequest, } @@ -23,16 +26,17 @@ pub struct SwapRequest { } impl SwapRequest { + /// Create new [`SwapRequest`] pub fn new(inputs: Proofs, outputs: Vec) -> Self { Self { inputs, outputs } } - /// Total value of proofs in `SplitRequest` + /// Total value of proofs in [`SwapRequest`] pub fn input_amount(&self) -> Amount { self.inputs.iter().map(|proof| proof.amount).sum() } - /// Total value of outputs in `SplitRequest` + /// Total value of outputs in [`SwapRequest`] pub fn output_amount(&self) -> Amount { self.outputs.iter().map(|proof| proof.amount).sum() } @@ -46,12 +50,14 @@ pub struct SwapResponse { } impl SwapResponse { + /// Create new [`SwapRequest`] pub fn new(promises: Vec) -> SwapResponse { SwapResponse { signatures: promises, } } + /// Total [`Amount`] of promises pub fn promises_amount(&self) -> Amount { self.signatures .iter() diff --git a/crates/cdk/src/nuts/nut04.rs b/crates/cdk/src/nuts/nut04.rs index 174658d0..e0f3181a 100644 --- a/crates/cdk/src/nuts/nut04.rs +++ b/crates/cdk/src/nuts/nut04.rs @@ -14,6 +14,7 @@ use super::MintQuoteState; use crate::types::MintQuote; use crate::Amount; +/// NUT04 Error #[derive(Debug, Error)] pub enum Error { /// Unknown Quote State @@ -34,10 +35,14 @@ pub struct MintQuoteBolt11Request { #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Default, Serialize, Deserialize)] #[serde(rename_all = "UPPERCASE")] pub enum QuoteState { + /// Quote has not been paid #[default] Unpaid, + /// Quote has been paid and wallet can mint Paid, + /// Minting is in progress Pending, + /// ecash issued for quote Issued, } @@ -171,6 +176,7 @@ pub struct MintBolt11Request { } impl MintBolt11Request { + /// Total [`Amount`] of outputs pub fn total_amount(&self) -> Amount { self.outputs .iter() diff --git a/crates/cdk/src/nuts/nut05.rs b/crates/cdk/src/nuts/nut05.rs index 24cd17c7..f33b1512 100644 --- a/crates/cdk/src/nuts/nut05.rs +++ b/crates/cdk/src/nuts/nut05.rs @@ -14,6 +14,7 @@ use super::nut15::Mpp; use crate::types::MeltQuote; use crate::{Amount, Bolt11Invoice}; +/// NUT05 Error #[derive(Debug, Error)] pub enum Error { /// Unknown Quote State @@ -36,9 +37,12 @@ pub struct MeltQuoteBolt11Request { #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Default, Serialize, Deserialize)] #[serde(rename_all = "UPPERCASE")] pub enum QuoteState { + /// Quote has not been paid #[default] Unpaid, + /// Quote has been paid Paid, + /// Paying quote is in progress Pending, } @@ -203,6 +207,7 @@ pub struct MeltBolt11Request { } impl MeltBolt11Request { + /// Total [`Amount`] of [`Proofs`] pub fn proofs_amount(&self) -> Amount { self.inputs.iter().map(|proof| proof.amount).sum() } diff --git a/crates/cdk/src/nuts/nut06.rs b/crates/cdk/src/nuts/nut06.rs index 322c817d..fd4ffca9 100644 --- a/crates/cdk/src/nuts/nut06.rs +++ b/crates/cdk/src/nuts/nut06.rs @@ -13,7 +13,9 @@ use super::{nut04, nut05, nut15}; /// Mint Version #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct MintVersion { + /// Mint Software name pub name: String, + /// Mint Version pub version: String, } @@ -76,36 +78,47 @@ pub struct MintInfo { /// Supported nuts and settings #[derive(Debug, Default, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] pub struct Nuts { + /// NUT04 Settings #[serde(default)] #[serde(rename = "4")] pub nut04: nut04::Settings, + /// NUT05 Settings #[serde(default)] #[serde(rename = "5")] pub nut05: nut05::Settings, + /// NUT07 Settings #[serde(default)] #[serde(rename = "7")] pub nut07: SupportedSettings, + /// NUT08 Settings #[serde(default)] #[serde(rename = "8")] pub nut08: SupportedSettings, + /// NUT09 Settings #[serde(default)] #[serde(rename = "9")] pub nut09: SupportedSettings, + /// NUT10 Settings #[serde(rename = "10")] #[serde(default)] pub nut10: SupportedSettings, + /// NUT11 Settings #[serde(rename = "11")] #[serde(default)] pub nut11: SupportedSettings, + /// NUT12 Settings #[serde(default)] #[serde(rename = "12")] pub nut12: SupportedSettings, + /// NUT13 Settings #[serde(default)] #[serde(rename = "13")] pub nut13: SupportedSettings, + /// NUT14 Settings #[serde(default)] #[serde(rename = "14")] pub nut14: SupportedSettings, + /// NUT15 Settings #[serde(default)] #[serde(rename = "15")] pub nut15: nut15::MppMethodSettings, diff --git a/crates/cdk/src/nuts/nut07.rs b/crates/cdk/src/nuts/nut07.rs index d5eceba2..5ee81ad7 100644 --- a/crates/cdk/src/nuts/nut07.rs +++ b/crates/cdk/src/nuts/nut07.rs @@ -18,12 +18,21 @@ pub enum Error { UnknownState, } +/// State of Proof #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "UPPERCASE")] pub enum State { + /// Spent Spent, + /// Unspent Unspent, + /// Pending + /// + /// Currently being used in a transaction i.e. melt in progress Pending, + /// Proof is reserved + /// + /// i.e. used to create a token Reserved, } @@ -77,5 +86,6 @@ pub struct ProofState { /// Check Spendable Response [NUT-07] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct CheckStateResponse { + /// Proof states pub states: Vec, } diff --git a/crates/cdk/src/nuts/nut08.rs b/crates/cdk/src/nuts/nut08.rs index 87fc9a7d..ce723897 100644 --- a/crates/cdk/src/nuts/nut08.rs +++ b/crates/cdk/src/nuts/nut08.rs @@ -6,6 +6,7 @@ use super::nut05::{MeltBolt11Request, MeltQuoteBolt11Response}; use crate::Amount; impl MeltBolt11Request { + /// Total output [`Amount`] pub fn output_amount(&self) -> Option { self.outputs .as_ref() @@ -14,6 +15,7 @@ impl MeltBolt11Request { } impl MeltQuoteBolt11Response { + /// Total change [`Amount`] pub fn change_amount(&self) -> Option { self.change .as_ref() diff --git a/crates/cdk/src/nuts/nut10.rs b/crates/cdk/src/nuts/nut10.rs index 3033af36..33b9866e 100644 --- a/crates/cdk/src/nuts/nut10.rs +++ b/crates/cdk/src/nuts/nut10.rs @@ -18,6 +18,7 @@ pub enum Kind { HTLC, } +/// Secert Date #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] pub struct SecretData { /// Unique random string @@ -29,6 +30,7 @@ pub struct SecretData { pub tags: Option>>, } +/// NUT10 Secret #[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize)] pub struct Secret { /// Kind of the spending condition @@ -38,6 +40,7 @@ pub struct Secret { } impl Secret { + /// Create new [`Secret`] pub fn new(kind: Kind, data: S, tags: Option) -> Self where S: Into, diff --git a/crates/cdk/src/nuts/nut11/mod.rs b/crates/cdk/src/nuts/nut11/mod.rs index f2e24407..541c9b4e 100644 --- a/crates/cdk/src/nuts/nut11/mod.rs +++ b/crates/cdk/src/nuts/nut11/mod.rs @@ -23,6 +23,7 @@ use crate::util::{hex, unix_time}; pub mod serde_p2pk_witness; +/// Nut11 Error #[derive(Debug, Error)] pub enum Error { /// Incorrect secret kind @@ -84,11 +85,13 @@ pub enum Error { /// P2Pk Witness #[derive(Default, Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] pub struct P2PKWitness { + /// Signatures pub signatures: Vec, } impl P2PKWitness { #[inline] + /// Check id Witness is empty pub fn is_empty(&self) -> bool { self.signatures.is_empty() } @@ -250,16 +253,27 @@ impl BlindedMessage { } } +/// Spending Conditions +/// +/// Defined in [NUT10](https://github.com/cashubtc/nuts/blob/main/10.md) #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] pub enum SpendingConditions { /// NUT11 Spending conditions + /// + /// Defined in [NUT11](https://github.com/cashubtc/nuts/blob/main/11.md) P2PKConditions { + /// The public key of the recipient of the locked ecash data: PublicKey, + /// Additional Optional Spending [`Conditions`] conditions: Option, }, /// NUT14 Spending conditions + /// + /// Dedined in [NUT14](https://github.com/cashubtc/nuts/blob/main/14.md) HTLCConditions { + /// Hash Lock of ecash data: Sha256Hash, + /// Additional Optional Spending [`Conditions`] conditions: Option, }, } @@ -291,6 +305,7 @@ impl SpendingConditions { } } + /// Number if signatures required to unlock pub fn num_sigs(&self) -> Option { match self { Self::P2PKConditions { conditions, .. } => conditions.as_ref().and_then(|c| c.num_sigs), @@ -298,6 +313,7 @@ impl SpendingConditions { } } + /// Public keys of locked [`Proof`] pub fn pubkeys(&self) -> Option> { match self { Self::P2PKConditions { data, conditions } => { @@ -312,6 +328,7 @@ impl SpendingConditions { } } + /// Locktime of Spending Conditions pub fn locktime(&self) -> Option { match self { Self::P2PKConditions { conditions, .. } => conditions.as_ref().and_then(|c| c.locktime), @@ -319,6 +336,7 @@ impl SpendingConditions { } } + /// Refund keys pub fn refund_keys(&self) -> Option> { match self { Self::P2PKConditions { conditions, .. } => { @@ -373,18 +391,28 @@ impl From for super::nut10::Secret { /// P2PK and HTLC spending conditions #[derive(Debug, Clone, PartialEq, Eq, Hash, Default, Serialize, Deserialize)] pub struct Conditions { + /// Unix locktime after which refund keys can be used #[serde(skip_serializing_if = "Option::is_none")] pub locktime: Option, + /// Additional Public keys #[serde(skip_serializing_if = "Option::is_none")] pub pubkeys: Option>, + /// Refund keys #[serde(skip_serializing_if = "Option::is_none")] pub refund_keys: Option>, + /// Numbedr of signatures required + /// + /// Default is 1 #[serde(skip_serializing_if = "Option::is_none")] pub num_sigs: Option, + /// Signature flag + /// + /// Default [`SigFlag::SigInputs`] pub sig_flag: SigFlag, } impl Conditions { + /// Create new Spending [`Conditions`] pub fn new( locktime: Option, pubkeys: Option>, @@ -499,7 +527,7 @@ impl TryFrom>> for Conditions { } } -// P2PK and HTLC Spending condition tags +/// P2PK and HTLC Spending condition tags #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, PartialOrd, Ord)] #[serde(rename_all = "lowercase")] pub enum TagKind { @@ -547,10 +575,16 @@ where } } +/// Signature flag +/// +/// Defined in [NUT11](https://github.com/cashubtc/nuts/blob/main/11.md) #[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize, PartialOrd, Ord, Hash)] pub enum SigFlag { #[default] + /// Requires valid signatures on all inputs. + /// It is the default signature flag and will be applied even if the `sigflag` tag is absent. SigInputs, + /// Requires valid signatures on all inputs and on all outputs. SigAll, } @@ -574,6 +608,7 @@ impl FromStr for SigFlag { } } +/// Get the signature flag that should be enforced for a set of proofs and the public keys that signatures are valid for pub fn enforce_sig_flag(proofs: Proofs) -> (SigFlag, HashSet) { let mut sig_flag = SigFlag::SigInputs; let mut pubkeys = HashSet::new(); @@ -602,16 +637,23 @@ pub fn enforce_sig_flag(proofs: Proofs) -> (SigFlag, HashSet) { (sig_flag, pubkeys) } +/// Tag #[derive(Debug, Clone, Hash, PartialEq, Eq)] pub enum Tag { + /// Sigflag [`Tag`] SigFlag(SigFlag), + /// Number of Sigs [`Tag`] NSigs(u64), + /// Locktime [`Tag`] LockTime(u64), + /// Refund [`Tag`] Refund(Vec), + /// Pubkeys [`Tag`] PubKeys(Vec), } impl Tag { + /// Get [`Tag`] Kind pub fn kind(&self) -> TagKind { match self { Self::SigFlag(_) => TagKind::SigFlag, diff --git a/crates/cdk/src/nuts/nut12.rs b/crates/cdk/src/nuts/nut12.rs index 812e2721..9b3da91f 100644 --- a/crates/cdk/src/nuts/nut12.rs +++ b/crates/cdk/src/nuts/nut12.rs @@ -14,36 +14,55 @@ use super::nut02::Id; use crate::dhke::{hash_e, hash_to_curve}; use crate::{Amount, SECP256K1}; +/// NUT12 Error #[derive(Debug, Error)] pub enum Error { + /// Missing Dleq Proof #[error("No Dleq Proof provided")] MissingDleqProof, + /// Incomplete Dleq Proof #[error("Incomplete DLEQ Proof")] IncompleteDleqProof, + /// Invalid Dleq Proof #[error("Invalid Dleq Prood")] InvalidDleqProof, + /// Cashu Error #[error(transparent)] Cashu(#[from] crate::error::Error), + /// NUT01 Error #[error(transparent)] NUT01(#[from] crate::nuts::nut01::Error), + /// SECP256k1 Error #[error(transparent)] Secp256k1(#[from] secp256k1::Error), } +/// Blinded Signature on Dleq +/// +/// Defined in [NUT12](https://github.com/cashubtc/nuts/blob/main/12.md) #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct BlindSignatureDleq { + /// e pub e: SecretKey, + /// s pub s: SecretKey, } +/// Proof Dleq +/// +/// Defined in [NUT12](https://github.com/cashubtc/nuts/blob/main/12.md) #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct ProofDleq { + /// e pub e: SecretKey, + /// s pub s: SecretKey, + /// Blinding factor pub r: SecretKey, } impl ProofDleq { + /// Create new [`ProofDleq`] pub fn new(e: SecretKey, s: SecretKey, r: SecretKey) -> Self { Self { e, s, r } } @@ -119,6 +138,7 @@ fn calculate_dleq( } impl Proof { + /// Verify proof Dleq pub fn verify_dleq(&self, mint_pubkey: PublicKey) -> Result<(), Error> { match &self.dleq { Some(dleq) => { @@ -165,6 +185,7 @@ impl BlindSignature { }) } + /// Verify dleq on proof #[inline] pub fn verify_dleq( &self, @@ -177,6 +198,7 @@ impl BlindSignature { } } + /// Add Dleq to proof /* r = random nonce R1 = r*G diff --git a/crates/cdk/src/nuts/nut13.rs b/crates/cdk/src/nuts/nut13.rs index 93814dce..a33d7c19 100644 --- a/crates/cdk/src/nuts/nut13.rs +++ b/crates/cdk/src/nuts/nut13.rs @@ -15,6 +15,7 @@ use crate::util::hex; use crate::{Amount, SECP256K1}; impl Secret { + /// Create new [`Secret`] from xpriv pub fn from_xpriv(xpriv: ExtendedPrivKey, keyset_id: Id, counter: u32) -> Result { let path = derive_path_from_keyset_id(keyset_id)? .child(ChildNumber::from_hardened_idx(counter)?) @@ -28,6 +29,7 @@ impl Secret { } impl SecretKey { + /// Create new [`SecretKey`] from xpriv pub fn from_xpriv(xpriv: ExtendedPrivKey, keyset_id: Id, counter: u32) -> Result { let path = derive_path_from_keyset_id(keyset_id)? .child(ChildNumber::from_hardened_idx(counter)?) @@ -74,6 +76,7 @@ impl PreMintSecrets { Ok(pre_mint_secrets) } + /// New [`PreMintSecrets`] from xpriv with a zero amount used for change pub fn from_xpriv_blank( keyset_id: Id, counter: u32, diff --git a/crates/cdk/src/nuts/nut14/mod.rs b/crates/cdk/src/nuts/nut14/mod.rs index 15244251..6e86ef1e 100644 --- a/crates/cdk/src/nuts/nut14/mod.rs +++ b/crates/cdk/src/nuts/nut14/mod.rs @@ -18,6 +18,7 @@ use crate::util::unix_time; pub mod serde_htlc_witness; +/// NUT14 Errors #[derive(Debug, Error)] pub enum Error { /// Incorrect secret kind @@ -42,12 +43,16 @@ pub enum Error { #[error(transparent)] NUT11(#[from] super::nut11::Error), #[error(transparent)] + /// Serde Error Serde(#[from] serde_json::Error), } +/// HTLC Witness #[derive(Default, Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] pub struct HTLCWitness { + /// Primage pub preimage: String, + /// Signatures #[serde(skip_serializing_if = "Option::is_none")] pub signatures: Option>, } @@ -125,6 +130,7 @@ impl Proof { Ok(()) } + /// Add Preimage #[inline] pub fn add_preimage(&mut self, preimage: String) { self.witness = Some(Witness::HTLCWitness(HTLCWitness { diff --git a/crates/cdk/src/nuts/nut14/serde_htlc_witness.rs b/crates/cdk/src/nuts/nut14/serde_htlc_witness.rs index 1224b4cf..fbeaab27 100644 --- a/crates/cdk/src/nuts/nut14/serde_htlc_witness.rs +++ b/crates/cdk/src/nuts/nut14/serde_htlc_witness.rs @@ -1,3 +1,5 @@ +//! Serde helpers for HTLC Witness + use serde::{de, ser, Deserialize, Deserializer, Serializer}; use super::HTLCWitness; diff --git a/crates/cdk/src/nuts/nut15.rs b/crates/cdk/src/nuts/nut15.rs index 869e4315..e61a861b 100644 --- a/crates/cdk/src/nuts/nut15.rs +++ b/crates/cdk/src/nuts/nut15.rs @@ -7,9 +7,11 @@ use serde::{Deserialize, Serialize}; use super::{CurrencyUnit, PaymentMethod}; use crate::Amount; +/// Multi-part payment #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] #[serde(rename = "lowercase")] pub struct Mpp { + /// Amount pub amount: Amount, } @@ -27,5 +29,6 @@ pub struct MppMethodSettings { /// Mpp Settings #[derive(Debug, Default, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] pub struct Settings { + /// Method settings pub methods: Vec, } diff --git a/crates/cdk/src/secret.rs b/crates/cdk/src/secret.rs index 451a5edb..423be092 100644 --- a/crates/cdk/src/secret.rs +++ b/crates/cdk/src/secret.rs @@ -1,6 +1,6 @@ //! Secret -use core::fmt; +use std::fmt; use std::str::FromStr; use bitcoin::secp256k1::rand::{self, RngCore}; @@ -14,10 +14,13 @@ use crate::util::hex; #[serde(transparent)] pub struct Secret(String); +/// Secret Errors #[derive(Debug, Error)] pub enum Error { + /// Invalid Length #[error("Invalid secret length: `{0}`")] InvalidLength(u64), + /// Hex Error #[error(transparent)] Hex(#[from] hex::Error), } @@ -29,6 +32,7 @@ impl Default for Secret { } impl Secret { + /// Create new [`Secret`] #[inline] pub fn new(secret: S) -> Self where @@ -51,16 +55,19 @@ impl Secret { Self(secret) } + /// [`Secret`] as bytes #[inline] pub fn as_bytes(&self) -> &[u8] { self.0.as_bytes() } + /// [`Secret`] to bytes #[inline] pub fn to_bytes(&self) -> Vec { self.as_bytes().to_vec() } + /// Check if secret is P2PK secret pub fn is_p2pk(&self) -> bool { use crate::nuts::Kind; diff --git a/crates/cdk/src/types.rs b/crates/cdk/src/types.rs index 75e38f1d..d57caef1 100644 --- a/crates/cdk/src/types.rs +++ b/crates/cdk/src/types.rs @@ -14,24 +14,35 @@ use crate::Amount; /// Melt response with proofs #[derive(Debug, Clone, Hash, PartialEq, Eq, Default, Serialize, Deserialize)] pub struct Melted { + /// State of quote pub state: MeltQuoteState, + /// Preimage of melt payment pub preimage: Option, + /// Melt change pub change: Option, } /// Mint Quote Info #[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)] pub struct MintQuote { + /// Quote id pub id: String, + /// Mint Url pub mint_url: UncheckedUrl, + /// Amount of quote pub amount: Amount, + /// Unit of quote pub unit: CurrencyUnit, + /// Quote payment request e.g. bolt11 pub request: String, + /// Quote state pub state: MintQuoteState, + /// Expiration time of quote pub expiry: u64, } impl MintQuote { + /// Create new [`MintQuote`] pub fn new( mint_url: UncheckedUrl, request: String, @@ -56,18 +67,27 @@ impl MintQuote { /// Melt Quote Info #[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)] pub struct MeltQuote { + /// Quote id pub id: String, + /// Quote unit pub unit: CurrencyUnit, + /// Quote amount pub amount: Amount, + /// Quote Payment request e.g. bolt11 pub request: String, + /// Quote fee reserve pub fee_reserve: Amount, + /// Quote state pub state: MeltQuoteState, + /// Expiration time of quote pub expiry: u64, + /// Payment preimage pub payment_preimage: Option, } #[cfg(feature = "mint")] impl MeltQuote { + /// Create new [`MeltQuote`] pub fn new( request: String, unit: CurrencyUnit, @@ -90,17 +110,25 @@ impl MeltQuote { } } +/// Prooinfo #[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)] pub struct ProofInfo { + /// Proof pub proof: Proof, + /// y pub y: PublicKey, + /// Mint Url pub mint_url: UncheckedUrl, + /// Proof State pub state: State, + /// Proof Spending Conditions pub spending_condition: Option, + /// Unit pub unit: CurrencyUnit, } impl ProofInfo { + /// Create new [`ProofInfo`] pub fn new( proof: Proof, mint_url: UncheckedUrl, @@ -123,6 +151,7 @@ impl ProofInfo { }) } + /// Check if [`Proof`] matches conditions pub fn matches_conditions( &self, mint_url: &Option, diff --git a/crates/cdk/src/url.rs b/crates/cdk/src/url.rs index 5e965605..69032a74 100644 --- a/crates/cdk/src/url.rs +++ b/crates/cdk/src/url.rs @@ -36,6 +36,7 @@ impl UncheckedUrl { Self(String::new()) } + /// Join onto url pub fn join(&self, path: &str) -> Result { let url: Url = self.try_into()?; Ok(url.join(path)?) diff --git a/crates/cdk/src/wallet/client.rs b/crates/cdk/src/wallet/client.rs index 05b994c6..3fddcc8e 100644 --- a/crates/cdk/src/wallet/client.rs +++ b/crates/cdk/src/wallet/client.rs @@ -36,6 +36,7 @@ fn join_url(url: Url, paths: &[&str]) -> Result { Ok(url) } +/// Http Client #[derive(Debug, Clone)] pub struct HttpClient { inner: Client, @@ -48,6 +49,7 @@ impl Default for HttpClient { } impl HttpClient { + /// Create new [`HttpClient`] pub fn new() -> Self { Self { inner: Client::new(), @@ -321,6 +323,7 @@ impl HttpClient { } } + /// Restore request [NUT-13] #[instrument(skip(self, request), fields(mint_url = %mint_url))] pub async fn post_restore( &self, diff --git a/crates/cdk/src/wallet/error.rs b/crates/cdk/src/wallet/error.rs index 045197c3..997877c7 100644 --- a/crates/cdk/src/wallet/error.rs +++ b/crates/cdk/src/wallet/error.rs @@ -1,3 +1,5 @@ +//! CDK Wallet Error + use std::num::ParseIntError; use thiserror::Error; @@ -5,6 +7,7 @@ use thiserror::Error; use crate::cdk_database; use crate::error::{ErrorCode, ErrorResponse}; +/// Wallet Error #[derive(Debug, Error)] pub enum Error { /// Insufficient Funds @@ -31,6 +34,7 @@ pub enum Error { /// Preimage not provided #[error("Preimage not provided")] PreimageNotProvided, + /// Unknown Key #[error("Unknown Key")] UnknownKey, /// Spending Locktime not provided @@ -102,14 +106,6 @@ pub enum Error { /// Serde Error #[error(transparent)] Serde(#[from] serde_json::Error), - /// Nostr Client Error - #[cfg(feature = "nostr")] - #[error(transparent)] - NostrClient(#[from] nostr_sdk::client::Error), - /// Nostr Key Error - #[cfg(feature = "nostr")] - #[error(transparent)] - NostrKey(#[from] nostr_sdk::key::Error), /// Custom Error #[error("`{0}`")] Custom(String), diff --git a/crates/cdk/src/wallet/mod.rs b/crates/cdk/src/wallet/mod.rs index a614d79d..8929387f 100644 --- a/crates/cdk/src/wallet/mod.rs +++ b/crates/cdk/src/wallet/mod.rs @@ -34,16 +34,21 @@ pub mod error; pub mod multi_mint_wallet; pub mod util; +/// CDK Wallet #[derive(Debug, Clone)] pub struct Wallet { + /// Mint Url pub mint_url: UncheckedUrl, + /// Unit pub unit: CurrencyUnit, - pub client: HttpClient, + client: HttpClient, + /// Storage backend pub localstore: Arc + Send + Sync>, xpriv: ExtendedPrivKey, } impl Wallet { + /// Create new [`Wallet`] pub fn new( mint_url: &str, unit: CurrencyUnit, @@ -278,6 +283,7 @@ impl Wallet { Ok(()) } + /// Get Active mint keyset id #[instrument(skip(self))] pub async fn active_mint_keyset(&self) -> Result { let mint_url = &self.mint_url; @@ -307,6 +313,7 @@ impl Wallet { Err(Error::NoActiveKeyset) } + /// Get active mint keys #[instrument(skip(self))] pub async fn active_keys(&self) -> Result, Error> { let active_keyset_id = self.active_mint_keyset().await?; @@ -1044,7 +1051,7 @@ impl Wallet { Ok(melted) } - // Select proofs + /// Select proofs #[instrument(skip(self))] pub async fn select_proofs( &self, @@ -1313,6 +1320,7 @@ impl Wallet { Ok(total_amount) } + /// Restore #[instrument(skip(self))] pub async fn restore(&self) -> Result { // Check that mint is in store of mints diff --git a/crates/cdk/src/wallet/multi_mint_wallet.rs b/crates/cdk/src/wallet/multi_mint_wallet.rs index b2451f30..ce67c5f7 100644 --- a/crates/cdk/src/wallet/multi_mint_wallet.rs +++ b/crates/cdk/src/wallet/multi_mint_wallet.rs @@ -17,11 +17,14 @@ use crate::nuts::{CurrencyUnit, SecretKey, SpendingConditions, Token}; use crate::types::{Melted, MintQuote}; use crate::{Amount, UncheckedUrl, Wallet}; +/// Multi Mint Wallet #[derive(Debug, Clone)] pub struct MultiMintWallet { + /// Wallets pub wallets: Arc>>, } +/// Wallet Key #[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)] pub struct WalletKey { mint_url: UncheckedUrl, @@ -35,6 +38,7 @@ impl fmt::Display for WalletKey { } impl WalletKey { + /// Create new [`WalletKey`] pub fn new(mint_url: UncheckedUrl, unit: CurrencyUnit) -> Self { Self { mint_url, unit } } @@ -255,7 +259,7 @@ impl MultiMintWallet { wallet.melt("e.id, SplitTarget::default()).await } - // Restore + /// Restore #[instrument(skip(self))] pub async fn restore(&self, wallet_key: &WalletKey) -> Result { let wallet = self diff --git a/crates/cdk/src/wallet/util.rs b/crates/cdk/src/wallet/util.rs index a62345cc..335a7a7d 100644 --- a/crates/cdk/src/wallet/util.rs +++ b/crates/cdk/src/wallet/util.rs @@ -27,7 +27,6 @@ pub fn proof_to_token( Ok(Token::new(mint_url, proofs, memo, unit)?) } -#[cfg(feature = "nostr")] #[cfg(test)] mod tests {