chore: readmes

chore: doc comments on public
This commit is contained in:
thesimplekid
2024-06-28 10:40:09 +01:00
parent aa65879482
commit b528964fb6
54 changed files with 598 additions and 109 deletions

View File

@@ -6,7 +6,19 @@
CDK is a collection of rust crates for [Cashu](https://github.com/cashubtc) wallets and mints written in Rust. 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/): ## Implemented [NUTs](https://github.com/cashubtc/nuts/):

View File

@@ -7,7 +7,7 @@ use cdk::amount::SplitTarget;
use cdk::nuts::{Proofs, SecretKey}; use cdk::nuts::{Proofs, SecretKey};
use cdk::wallet::Wallet; use cdk::wallet::Wallet;
use cdk::Amount; use cdk::Amount;
use cdk_rexie::RexieWalletDatabase; use cdk_rexie::WalletRexieDatabase;
use wasm_bindgen::prelude::*; use wasm_bindgen::prelude::*;
use crate::error::{into_err, Result}; use crate::error::{into_err, Result};
@@ -42,7 +42,7 @@ impl From<Wallet> for JsWallet {
impl JsWallet { impl JsWallet {
#[wasm_bindgen(constructor)] #[wasm_bindgen(constructor)]
pub async fn new(mints_url: String, unit: JsCurrencyUnit, seed: Vec<u8>) -> Self { pub async fn new(mints_url: String, unit: JsCurrencyUnit, seed: Vec<u8>) -> Self {
let db = RexieWalletDatabase::new().await.unwrap(); let db = WalletRexieDatabase::new().await.unwrap();
Wallet::new(&mints_url, unit.into(), Arc::new(db), &seed).into() Wallet::new(&mints_url, unit.into(), Arc::new(db), &seed).into()
} }

View File

@@ -9,8 +9,8 @@ use bip39::Mnemonic;
use cdk::cdk_database::WalletDatabase; use cdk::cdk_database::WalletDatabase;
use cdk::wallet::Wallet; use cdk::wallet::Wallet;
use cdk::{cdk_database, UncheckedUrl}; use cdk::{cdk_database, UncheckedUrl};
use cdk_redb::RedbWalletDatabase; use cdk_redb::WalletRedbDatabase;
use cdk_sqlite::WalletSQLiteDatabase; use cdk_sqlite::WalletSqliteDatabase;
use clap::{Parser, Subcommand}; use clap::{Parser, Subcommand};
use rand::Rng; use rand::Rng;
@@ -86,7 +86,7 @@ async fn main() -> Result<()> {
match args.engine.as_str() { match args.engine.as_str() {
"sqlite" => { "sqlite" => {
let sql_path = work_dir.join("cdk-cli.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; sql.migrate().await;
@@ -95,7 +95,7 @@ async fn main() -> Result<()> {
"redb" => { "redb" => {
let redb_path = work_dir.join("cdk-cli.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"), _ => bail!("Unknown DB engine"),
}; };

23
crates/cdk-redb/README.md Normal file
View File

@@ -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 <https://github.com/cashubtc/cdk/blob/main/README.md>
## License
This project is distributed under the MIT software license - see the [LICENSE](../../LICENSE) file for details

View File

@@ -1,7 +1,10 @@
//! Redb Error
use std::num::ParseIntError; use std::num::ParseIntError;
use thiserror::Error; use thiserror::Error;
/// Redb Database Error
#[derive(Debug, Error)] #[derive(Debug, Error)]
pub enum Error { pub enum Error {
/// Redb Error /// Redb Error

View File

@@ -1,3 +1,8 @@
//! SQLite Storage backend for CDK
#![warn(missing_docs)]
#![warn(rustdoc::bare_urls)]
pub mod error; pub mod error;
mod migrations; mod migrations;
@@ -9,4 +14,4 @@ pub mod wallet;
#[cfg(feature = "mint")] #[cfg(feature = "mint")]
pub use mint::MintRedbDatabase; pub use mint::MintRedbDatabase;
#[cfg(feature = "wallet")] #[cfg(feature = "wallet")]
pub use wallet::RedbWalletDatabase; pub use wallet::WalletRedbDatabase;

View File

@@ -1,3 +1,5 @@
//! SQLite Storage for CDK
use std::cmp::Ordering; use std::cmp::Ordering;
use std::collections::HashMap; use std::collections::HashMap;
use std::path::Path; use std::path::Path;
@@ -34,12 +36,14 @@ const BLINDED_SIGNATURES: TableDefinition<[u8; 33], &str> =
const DATABASE_VERSION: u32 = 0; const DATABASE_VERSION: u32 = 0;
/// Mint Redbdatabase
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct MintRedbDatabase { pub struct MintRedbDatabase {
db: Arc<Mutex<Database>>, db: Arc<Mutex<Database>>,
} }
impl MintRedbDatabase { impl MintRedbDatabase {
/// Create new [`MintRedbDatabase`]
pub fn new(path: &Path) -> Result<Self, Error> { pub fn new(path: &Path) -> Result<Self, Error> {
{ {
// Check database version // Check database version

View File

@@ -41,12 +41,14 @@ const NOSTR_LAST_CHECKED: TableDefinition<&str, u32> = TableDefinition::new("key
const DATABASE_VERSION: u32 = 1; const DATABASE_VERSION: u32 = 1;
/// Wallet Redb Database
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct RedbWalletDatabase { pub struct WalletRedbDatabase {
db: Arc<Mutex<Database>>, db: Arc<Mutex<Database>>,
} }
impl RedbWalletDatabase { impl WalletRedbDatabase {
/// Create new [`WalletRedbDatabase`]
pub fn new(path: &Path) -> Result<Self, Error> { pub fn new(path: &Path) -> Result<Self, Error> {
{ {
let db = Arc::new(Database::create(path)?); let db = Arc::new(Database::create(path)?);
@@ -132,7 +134,7 @@ impl RedbWalletDatabase {
} }
#[async_trait] #[async_trait]
impl WalletDatabase for RedbWalletDatabase { impl WalletDatabase for WalletRedbDatabase {
type Err = cdk_database::Error; type Err = cdk_database::Error;
#[instrument(skip(self))] #[instrument(skip(self))]

View File

@@ -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 <https://github.com/cashubtc/cdk/blob/main/README.md>
## License
This project is distributed under the MIT software license - see the [LICENSE](../../LICENSE) file for details.

View File

@@ -1,5 +1,10 @@
//! Rexie Indexdb database
#![warn(missing_docs)]
#![warn(rustdoc::bare_urls)]
#[cfg(all(feature = "wallet", target_arch = "wasm32"))] #[cfg(all(feature = "wallet", target_arch = "wasm32"))]
pub mod wallet; pub mod wallet;
#[cfg(all(feature = "wallet", target_arch = "wasm32"))] #[cfg(all(feature = "wallet", target_arch = "wasm32"))]
pub use wallet::RexieWalletDatabase; pub use wallet::WalletRexieDatabase;

View File

@@ -1,3 +1,5 @@
//! Rexie Browser Database
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
use std::rc::Rc; use std::rc::Rc;
use std::result::Result; use std::result::Result;
@@ -28,6 +30,7 @@ const NOSTR_LAST_CHECKED: &str = "nostr_last_check";
const DATABASE_VERSION: u32 = 3; const DATABASE_VERSION: u32 = 3;
/// Rexie Database Error
#[derive(Debug, Error)] #[derive(Debug, Error)]
pub enum Error { pub enum Error {
/// CDK Database Error /// CDK Database Error
@@ -39,6 +42,7 @@ pub enum Error {
/// Serde Wasm Error /// Serde Wasm Error
#[error(transparent)] #[error(transparent)]
SerdeBindgen(#[from] serde_wasm_bindgen::Error), SerdeBindgen(#[from] serde_wasm_bindgen::Error),
/// NUT00 Error
#[error(transparent)] #[error(transparent)]
NUT00(cdk::nuts::nut00::Error), NUT00(cdk::nuts::nut00::Error),
} }
@@ -52,16 +56,18 @@ impl From<Error> for cdk::cdk_database::Error {
unsafe impl Send for Error {} unsafe impl Send for Error {}
unsafe impl Sync for Error {} unsafe impl Sync for Error {}
/// Wallet Rexie Database
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct RexieWalletDatabase { pub struct WalletRexieDatabase {
db: Rc<Mutex<Rexie>>, db: Rc<Mutex<Rexie>>,
} }
// These are okay because we never actually send across threads in the browser // These are okay because we never actually send across threads in the browser
unsafe impl Send for RexieWalletDatabase {} unsafe impl Send for WalletRexieDatabase {}
unsafe impl Sync for RexieWalletDatabase {} unsafe impl Sync for WalletRexieDatabase {}
impl RexieWalletDatabase { impl WalletRexieDatabase {
/// Create new [`WalletRexieDatabase`]
pub async fn new() -> Result<Self, Error> { pub async fn new() -> Result<Self, Error> {
let rexie = Rexie::builder("cdk") let rexie = Rexie::builder("cdk")
.version(DATABASE_VERSION) .version(DATABASE_VERSION)
@@ -102,7 +108,7 @@ impl RexieWalletDatabase {
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))] #[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)] #[cfg_attr(not(target_arch = "wasm32"), async_trait)]
impl WalletDatabase for RexieWalletDatabase { impl WalletDatabase for WalletRexieDatabase {
type Err = cdk::cdk_database::Error; type Err = cdk::cdk_database::Error;
async fn add_mint( async fn add_mint(

View File

@@ -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 <https://github.com/cashubtc/cdk/blob/main/README.md>
## License
This project is distributed under the MIT software license - see the [LICENSE](../../LICENSE) file for details.

View File

@@ -1,3 +1,8 @@
//! SQLite storage backend for cdk
#![warn(missing_docs)]
#![warn(rustdoc::bare_urls)]
#[cfg(feature = "mint")] #[cfg(feature = "mint")]
pub mod mint; pub mod mint;
#[cfg(feature = "wallet")] #[cfg(feature = "wallet")]
@@ -6,4 +11,4 @@ pub mod wallet;
#[cfg(feature = "mint")] #[cfg(feature = "mint")]
pub use mint::MintSqliteDatabase; pub use mint::MintSqliteDatabase;
#[cfg(feature = "wallet")] #[cfg(feature = "wallet")]
pub use wallet::WalletSQLiteDatabase; pub use wallet::WalletSqliteDatabase;

View File

@@ -1,5 +1,8 @@
//! SQLite Database Error
use thiserror::Error; use thiserror::Error;
/// SQLite Database Error
#[derive(Debug, Error)] #[derive(Debug, Error)]
pub enum Error { pub enum Error {
/// SQLX Error /// SQLX Error

View File

@@ -21,12 +21,14 @@ use sqlx::{ConnectOptions, Row};
pub mod error; pub mod error;
/// Mint SQLite Database
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct MintSqliteDatabase { pub struct MintSqliteDatabase {
pool: SqlitePool, pool: SqlitePool,
} }
impl MintSqliteDatabase { impl MintSqliteDatabase {
/// Create new [`MintSqliteDatabase`]
pub async fn new(path: &Path) -> Result<Self, Error> { pub async fn new(path: &Path) -> Result<Self, Error> {
let path = path.to_str().ok_or(Error::InvalidDbPath)?; let path = path.to_str().ok_or(Error::InvalidDbPath)?;
let _conn = SqliteConnectOptions::from_str(path)? let _conn = SqliteConnectOptions::from_str(path)?
@@ -42,6 +44,7 @@ impl MintSqliteDatabase {
Ok(Self { pool }) Ok(Self { pool })
} }
/// Migrate [`MintSqliteDatabase`]
pub async fn migrate(&self) { pub async fn migrate(&self) {
sqlx::migrate!("./src/mint/migrations") sqlx::migrate!("./src/mint/migrations")
.run(&self.pool) .run(&self.pool)

View File

@@ -1,5 +1,8 @@
//! SQLite Wallet Error
use thiserror::Error; use thiserror::Error;
/// SQLite Wallet Error
#[derive(Debug, Error)] #[derive(Debug, Error)]
pub enum Error { pub enum Error {
/// SQLX Error /// SQLX Error

View File

@@ -20,12 +20,14 @@ use sqlx::{ConnectOptions, Row};
pub mod error; pub mod error;
/// Wallet SQLite Database
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct WalletSQLiteDatabase { pub struct WalletSqliteDatabase {
pool: SqlitePool, pool: SqlitePool,
} }
impl WalletSQLiteDatabase { impl WalletSqliteDatabase {
/// Create new [`WalletSqliteDatabase`]
pub async fn new(path: &Path) -> Result<Self, Error> { pub async fn new(path: &Path) -> Result<Self, Error> {
let path = path.to_str().ok_or(Error::InvalidDbPath)?; let path = path.to_str().ok_or(Error::InvalidDbPath)?;
let _conn = SqliteConnectOptions::from_str(path)? let _conn = SqliteConnectOptions::from_str(path)?
@@ -41,6 +43,7 @@ impl WalletSQLiteDatabase {
Ok(Self { pool }) Ok(Self { pool })
} }
/// Migrate [`WalletSqliteDatabase`]
pub async fn migrate(&self) { pub async fn migrate(&self) {
sqlx::migrate!("./src/wallet/migrations") sqlx::migrate!("./src/wallet/migrations")
.run(&self.pool) .run(&self.pool)
@@ -50,7 +53,7 @@ impl WalletSQLiteDatabase {
} }
#[async_trait] #[async_trait]
impl WalletDatabase for WalletSQLiteDatabase { impl WalletDatabase for WalletSqliteDatabase {
type Err = cdk_database::Error; type Err = cdk_database::Error;
async fn add_mint( async fn add_mint(

View File

@@ -1,19 +1,23 @@
# Cashu Development Kit # 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/): ## Implemented [NUTs](https://github.com/cashubtc/nuts/):
- :heavy_check_mark: [NUT-00](https://github.com/cashubtc/nuts/blob/main/00.md) See <https://github.com/cashubtc/cdk/blob/main/README.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) ## License
- :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) This project is distributed under the MIT software license - see the [LICENSE](../../LICENSE) file for details
- :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)

View File

@@ -1,3 +1,7 @@
//! CDK Amount
//!
//! Is any and will be treated as the unit of the wallet
use std::fmt; use std::fmt;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@@ -8,6 +12,7 @@ use serde::{Deserialize, Serialize};
pub struct Amount(u64); pub struct Amount(u64);
impl Amount { impl Amount {
/// Amount zero
pub const ZERO: Amount = Amount(0); pub const ZERO: Amount = Amount(0);
/// Split into parts that are powers of two /// Split into parts that are powers of two

View File

@@ -1,3 +1,5 @@
//! Mint in memory database
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::Arc; use std::sync::Arc;
@@ -13,6 +15,7 @@ use crate::nuts::{
use crate::secret::Secret; use crate::secret::Secret;
use crate::types::{MeltQuote, MintQuote}; use crate::types::{MeltQuote, MintQuote};
/// Mint Memory Database
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct MintMemoryDatabase { pub struct MintMemoryDatabase {
active_keysets: Arc<RwLock<HashMap<CurrencyUnit, Id>>>, active_keysets: Arc<RwLock<HashMap<CurrencyUnit, Id>>>,
@@ -25,6 +28,7 @@ pub struct MintMemoryDatabase {
} }
impl MintMemoryDatabase { impl MintMemoryDatabase {
/// Create new [`MintMemoryDatabase`]
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
pub fn new( pub fn new(
active_keysets: HashMap<CurrencyUnit, Id>, active_keysets: HashMap<CurrencyUnit, Id>,

View File

@@ -35,63 +35,90 @@ pub mod wallet_memory;
#[cfg(feature = "wallet")] #[cfg(feature = "wallet")]
pub use wallet_memory::WalletMemoryDatabase; pub use wallet_memory::WalletMemoryDatabase;
/// CDK_database error
#[derive(Debug, Error)] #[derive(Debug, Error)]
pub enum Error { pub enum Error {
/// Database Error
#[error(transparent)] #[error(transparent)]
Database(Box<dyn std::error::Error + Send + Sync>), Database(Box<dyn std::error::Error + Send + Sync>),
/// CDK Error
#[error(transparent)] #[error(transparent)]
Cdk(#[from] crate::error::Error), Cdk(#[from] crate::error::Error),
/// NUT01 Error
#[error(transparent)] #[error(transparent)]
NUT01(#[from] crate::nuts::nut00::Error), NUT01(#[from] crate::nuts::nut00::Error),
/// Unknown Quote
#[error("Unknown Quote")] #[error("Unknown Quote")]
UnknownQuote, UnknownQuote,
} }
/// Wallet Database trait
#[cfg(feature = "wallet")] #[cfg(feature = "wallet")]
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))] #[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)] #[cfg_attr(not(target_arch = "wasm32"), async_trait)]
pub trait WalletDatabase: Debug { pub trait WalletDatabase: Debug {
/// Wallet Database Error
type Err: Into<Error> + From<Error>; type Err: Into<Error> + From<Error>;
/// Add Mint to storage
async fn add_mint( async fn add_mint(
&self, &self,
mint_url: UncheckedUrl, mint_url: UncheckedUrl,
mint_info: Option<MintInfo>, mint_info: Option<MintInfo>,
) -> Result<(), Self::Err>; ) -> Result<(), Self::Err>;
/// Remove Mint from storage
async fn remove_mint(&self, mint_url: UncheckedUrl) -> Result<(), Self::Err>; async fn remove_mint(&self, mint_url: UncheckedUrl) -> Result<(), Self::Err>;
/// Get mint from storage
async fn get_mint(&self, mint_url: UncheckedUrl) -> Result<Option<MintInfo>, Self::Err>; async fn get_mint(&self, mint_url: UncheckedUrl) -> Result<Option<MintInfo>, Self::Err>;
/// Get all mints from storage
async fn get_mints(&self) -> Result<HashMap<UncheckedUrl, Option<MintInfo>>, Self::Err>; async fn get_mints(&self) -> Result<HashMap<UncheckedUrl, Option<MintInfo>>, Self::Err>;
/// Update mint url
async fn update_mint_url( async fn update_mint_url(
&self, &self,
old_mint_url: UncheckedUrl, old_mint_url: UncheckedUrl,
new_mint_url: UncheckedUrl, new_mint_url: UncheckedUrl,
) -> Result<(), Self::Err>; ) -> Result<(), Self::Err>;
/// Add mint keyset to storage
async fn add_mint_keysets( async fn add_mint_keysets(
&self, &self,
mint_url: UncheckedUrl, mint_url: UncheckedUrl,
keysets: Vec<KeySetInfo>, keysets: Vec<KeySetInfo>,
) -> Result<(), Self::Err>; ) -> Result<(), Self::Err>;
/// Get mint keysets for mint url
async fn get_mint_keysets( async fn get_mint_keysets(
&self, &self,
mint_url: UncheckedUrl, mint_url: UncheckedUrl,
) -> Result<Option<Vec<KeySetInfo>>, Self::Err>; ) -> Result<Option<Vec<KeySetInfo>>, Self::Err>;
/// Get mint keyset by id
async fn get_keyset_by_id(&self, keyset_id: &Id) -> Result<Option<KeySetInfo>, Self::Err>; async fn get_keyset_by_id(&self, keyset_id: &Id) -> Result<Option<KeySetInfo>, Self::Err>;
/// Add mint quote to storage
async fn add_mint_quote(&self, quote: MintQuote) -> Result<(), Self::Err>; 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<Option<MintQuote>, Self::Err>; async fn get_mint_quote(&self, quote_id: &str) -> Result<Option<MintQuote>, Self::Err>;
/// Get mint quotes from storage
async fn get_mint_quotes(&self) -> Result<Vec<MintQuote>, Self::Err>; async fn get_mint_quotes(&self) -> Result<Vec<MintQuote>, Self::Err>;
/// Remove mint quote from storage
async fn remove_mint_quote(&self, quote_id: &str) -> Result<(), Self::Err>; 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>; 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<Option<MeltQuote>, Self::Err>; async fn get_melt_quote(&self, quote_id: &str) -> Result<Option<MeltQuote>, Self::Err>;
/// Remove melt quote from storage
async fn remove_melt_quote(&self, quote_id: &str) -> Result<(), Self::Err>; 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>; async fn add_keys(&self, keys: Keys) -> Result<(), Self::Err>;
/// Get [`Keys`] from storage
async fn get_keys(&self, id: &Id) -> Result<Option<Keys>, Self::Err>; async fn get_keys(&self, id: &Id) -> Result<Option<Keys>, Self::Err>;
/// Remove [`Keys`] from storage
async fn remove_keys(&self, id: &Id) -> Result<(), Self::Err>; async fn remove_keys(&self, id: &Id) -> Result<(), Self::Err>;
/// Add [`Proofs`] to storage
async fn add_proofs(&self, proof_info: Vec<ProofInfo>) -> Result<(), Self::Err>; async fn add_proofs(&self, proof_info: Vec<ProofInfo>) -> Result<(), Self::Err>;
/// Get proofs from storage
async fn get_proofs( async fn get_proofs(
&self, &self,
mint_url: Option<UncheckedUrl>, mint_url: Option<UncheckedUrl>,
@@ -99,17 +126,23 @@ pub trait WalletDatabase: Debug {
state: Option<Vec<State>>, state: Option<Vec<State>>,
spending_conditions: Option<Vec<SpendingConditions>>, spending_conditions: Option<Vec<SpendingConditions>>,
) -> Result<Option<Vec<ProofInfo>>, Self::Err>; ) -> Result<Option<Vec<ProofInfo>>, Self::Err>;
/// Remove proofs from storage
async fn remove_proofs(&self, proofs: &Proofs) -> Result<(), Self::Err>; 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>; 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>; 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<Option<u32>, Self::Err>; async fn get_keyset_counter(&self, keyset_id: &Id) -> Result<Option<u32>, Self::Err>;
/// Get when nostr key was last checked
async fn get_nostr_last_checked( async fn get_nostr_last_checked(
&self, &self,
verifying_key: &PublicKey, verifying_key: &PublicKey,
) -> Result<Option<u32>, Self::Err>; ) -> Result<Option<u32>, Self::Err>;
/// Update last checked time
async fn add_nostr_last_checked( async fn add_nostr_last_checked(
&self, &self,
verifying_key: PublicKey, verifying_key: PublicKey,
@@ -117,60 +150,88 @@ pub trait WalletDatabase: Debug {
) -> Result<(), Self::Err>; ) -> Result<(), Self::Err>;
} }
/// Mint Database trait
#[cfg(feature = "mint")] #[cfg(feature = "mint")]
#[async_trait] #[async_trait]
pub trait MintDatabase { pub trait MintDatabase {
/// Mint Database Error
type Err: Into<Error> + From<Error>; type Err: Into<Error> + From<Error>;
/// Add Active Keyset
async fn add_active_keyset(&self, unit: CurrencyUnit, id: Id) -> Result<(), Self::Err>; 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<Option<Id>, Self::Err>; async fn get_active_keyset_id(&self, unit: &CurrencyUnit) -> Result<Option<Id>, Self::Err>;
/// Get all Active Keyset
async fn get_active_keysets(&self) -> Result<HashMap<CurrencyUnit, Id>, Self::Err>; async fn get_active_keysets(&self) -> Result<HashMap<CurrencyUnit, Id>, Self::Err>;
/// Add [`MintQuote`]
async fn add_mint_quote(&self, quote: MintQuote) -> Result<(), Self::Err>; async fn add_mint_quote(&self, quote: MintQuote) -> Result<(), Self::Err>;
/// Get [`MintQuote`]
async fn get_mint_quote(&self, quote_id: &str) -> Result<Option<MintQuote>, Self::Err>; async fn get_mint_quote(&self, quote_id: &str) -> Result<Option<MintQuote>, Self::Err>;
/// Update state of [`MintQuote`]
async fn update_mint_quote_state( async fn update_mint_quote_state(
&self, &self,
quote_id: &str, quote_id: &str,
state: MintQuoteState, state: MintQuoteState,
) -> Result<MintQuoteState, Self::Err>; ) -> Result<MintQuoteState, Self::Err>;
/// Get all [`MintQuote`]s
async fn get_mint_quotes(&self) -> Result<Vec<MintQuote>, Self::Err>; async fn get_mint_quotes(&self) -> Result<Vec<MintQuote>, Self::Err>;
/// Remove [`MintQuote`]
async fn remove_mint_quote(&self, quote_id: &str) -> Result<(), Self::Err>; 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>; async fn add_melt_quote(&self, quote: MeltQuote) -> Result<(), Self::Err>;
/// Get [`MeltQuote`]
async fn get_melt_quote(&self, quote_id: &str) -> Result<Option<MeltQuote>, Self::Err>; async fn get_melt_quote(&self, quote_id: &str) -> Result<Option<MeltQuote>, Self::Err>;
/// Update [`MeltQuote`] state
async fn update_melt_quote_state( async fn update_melt_quote_state(
&self, &self,
quote_id: &str, quote_id: &str,
state: MeltQuoteState, state: MeltQuoteState,
) -> Result<MeltQuoteState, Self::Err>; ) -> Result<MeltQuoteState, Self::Err>;
/// Get all [`MeltQuote`]s
async fn get_melt_quotes(&self) -> Result<Vec<MeltQuote>, Self::Err>; async fn get_melt_quotes(&self) -> Result<Vec<MeltQuote>, Self::Err>;
/// Remove [`MeltQuote`]
async fn remove_melt_quote(&self, quote_id: &str) -> Result<(), Self::Err>; 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>; async fn add_keyset_info(&self, keyset: MintKeySetInfo) -> Result<(), Self::Err>;
/// Get [`MintKeySetInfo`]
async fn get_keyset_info(&self, id: &Id) -> Result<Option<MintKeySetInfo>, Self::Err>; async fn get_keyset_info(&self, id: &Id) -> Result<Option<MintKeySetInfo>, Self::Err>;
/// Get [`MintKeySetInfo`]s
async fn get_keyset_infos(&self) -> Result<Vec<MintKeySetInfo>, Self::Err>; async fn get_keyset_infos(&self) -> Result<Vec<MintKeySetInfo>, Self::Err>;
/// Add spent [`Proofs`]
async fn add_spent_proofs(&self, proof: Proofs) -> Result<(), Self::Err>; 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<Option<Proof>, Self::Err>; async fn get_spent_proof_by_secret(&self, secret: &Secret) -> Result<Option<Proof>, Self::Err>;
/// Get spent [`Proof`] by y
async fn get_spent_proof_by_y(&self, y: &PublicKey) -> Result<Option<Proof>, Self::Err>; async fn get_spent_proof_by_y(&self, y: &PublicKey) -> Result<Option<Proof>, Self::Err>;
/// Add pending [`Proofs`]
async fn add_pending_proofs(&self, proof: Proofs) -> Result<(), Self::Err>; async fn add_pending_proofs(&self, proof: Proofs) -> Result<(), Self::Err>;
/// Get pending [`Proof`] by secret
async fn get_pending_proof_by_secret( async fn get_pending_proof_by_secret(
&self, &self,
secret: &Secret, secret: &Secret,
) -> Result<Option<Proof>, Self::Err>; ) -> Result<Option<Proof>, Self::Err>;
/// Get pending [`Proof`] by y
async fn get_pending_proof_by_y(&self, y: &PublicKey) -> Result<Option<Proof>, Self::Err>; async fn get_pending_proof_by_y(&self, y: &PublicKey) -> Result<Option<Proof>, Self::Err>;
/// Remove pending [`Proofs`]
async fn remove_pending_proofs(&self, secret: Vec<&Secret>) -> Result<(), Self::Err>; async fn remove_pending_proofs(&self, secret: Vec<&Secret>) -> Result<(), Self::Err>;
/// Add [`BlindSignature`]
async fn add_blinded_signature( async fn add_blinded_signature(
&self, &self,
blinded_message: PublicKey, blinded_message: PublicKey,
blinded_signature: BlindSignature, blinded_signature: BlindSignature,
) -> Result<(), Self::Err>; ) -> Result<(), Self::Err>;
/// Get [`BlindSignature`]
async fn get_blinded_signature( async fn get_blinded_signature(
&self, &self,
blinded_message: &PublicKey, blinded_message: &PublicKey,
) -> Result<Option<BlindSignature>, Self::Err>; ) -> Result<Option<BlindSignature>, Self::Err>;
/// Get [`BlindSignature`]s
async fn get_blinded_signatures( async fn get_blinded_signatures(
&self, &self,
blinded_messages: Vec<PublicKey>, blinded_messages: Vec<PublicKey>,

View File

@@ -1,4 +1,4 @@
//! Memory Database //! Wallet in memory database
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
use std::sync::Arc; use std::sync::Arc;
@@ -15,6 +15,7 @@ use crate::types::{MeltQuote, MintQuote, ProofInfo};
use crate::url::UncheckedUrl; use crate::url::UncheckedUrl;
use crate::util::unix_time; use crate::util::unix_time;
/// Wallet in Memory Database
#[derive(Default, Debug, Clone)] #[derive(Default, Debug, Clone)]
pub struct WalletMemoryDatabase { pub struct WalletMemoryDatabase {
mints: Arc<RwLock<HashMap<UncheckedUrl, Option<MintInfo>>>>, mints: Arc<RwLock<HashMap<UncheckedUrl, Option<MintInfo>>>>,
@@ -29,6 +30,7 @@ pub struct WalletMemoryDatabase {
} }
impl WalletMemoryDatabase { impl WalletMemoryDatabase {
/// Create new [`WalletMemoryDatabase`]
pub fn new( pub fn new(
mint_quotes: Vec<MintQuote>, mint_quotes: Vec<MintQuote>,
melt_quotes: Vec<MeltQuote>, melt_quotes: Vec<MeltQuote>,

View File

@@ -18,6 +18,9 @@ use crate::SECP256K1;
const DOMAIN_SEPARATOR: &[u8; 28] = b"Secp256k1_HashToCurve_Cashu_"; 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<PublicKey, Error> { pub fn hash_to_curve(message: &[u8]) -> Result<PublicKey, Error> {
let msg_to_hash: Vec<u8> = [DOMAIN_SEPARATOR, message].concat(); let msg_to_hash: Vec<u8> = [DOMAIN_SEPARATOR, message].concat();
@@ -44,6 +47,7 @@ pub fn hash_to_curve(message: &[u8]) -> Result<PublicKey, Error> {
Err(Error::NoValidPoint) Err(Error::NoValidPoint)
} }
/// Convert iterator of [`PublicKey`] to byte array
pub fn hash_e<I>(public_keys: I) -> [u8; 32] pub fn hash_e<I>(public_keys: I) -> [u8; 32]
where where
I: IntoIterator<Item = PublicKey>, I: IntoIterator<Item = PublicKey>,
@@ -158,7 +162,7 @@ pub fn verify_message(
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use core::str::FromStr; use std::str::FromStr;
use super::*; use super::*;

View File

@@ -9,6 +9,7 @@ use thiserror::Error;
use crate::util::hex; use crate::util::hex;
/// CDK Error
#[derive(Debug, Error)] #[derive(Debug, Error)]
pub enum Error { pub enum Error {
/// Mint does not have a key for amount /// Mint does not have a key for amount
@@ -77,10 +78,16 @@ pub enum Error {
CustomError(String), 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)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ErrorResponse { pub struct ErrorResponse {
/// Error Code
pub code: ErrorCode, pub code: ErrorCode,
/// Human readable Text
pub error: Option<String>, pub error: Option<String>,
/// Longer human readable description
pub detail: Option<String>, pub detail: Option<String>,
} }
@@ -97,12 +104,14 @@ impl fmt::Display for ErrorResponse {
} }
impl ErrorResponse { impl ErrorResponse {
/// Error response from json
pub fn from_json(json: &str) -> Result<Self, serde_json::Error> { pub fn from_json(json: &str) -> Result<Self, serde_json::Error> {
let value: Value = serde_json::from_str(json)?; let value: Value = serde_json::from_str(json)?;
Self::from_value(value) Self::from_value(value)
} }
/// Error response from json Value
pub fn from_value(value: Value) -> Result<Self, serde_json::Error> { pub fn from_value(value: Value) -> Result<Self, serde_json::Error> {
match serde_json::from_value::<ErrorResponse>(value.clone()) { match serde_json::from_value::<ErrorResponse>(value.clone()) {
Ok(res) => Ok(res), Ok(res) => Ok(res),
@@ -115,11 +124,16 @@ impl ErrorResponse {
} }
} }
/// Possible Error Codes
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
pub enum ErrorCode { pub enum ErrorCode {
/// Token is already spent
TokenAlreadySpent, TokenAlreadySpent,
/// Quote is not paid
QuoteNotPaid, QuoteNotPaid,
/// Keyset is not found
KeysetNotFound, KeysetNotFound,
/// Unknown error code
Unknown(u16), Unknown(u16),
} }

View File

@@ -1,8 +1,7 @@
extern crate core; //! Rust implementation of the Cashu Protocol
pub use bitcoin::hashes::sha256::Hash as Sha256; #![warn(missing_docs)]
pub use bitcoin::secp256k1; #![warn(rustdoc::bare_urls)]
pub use lightning_invoice::{self, Bolt11Invoice};
pub mod amount; pub mod amount;
pub mod cdk_database; pub mod cdk_database;
@@ -18,15 +17,27 @@ pub mod util;
#[cfg(feature = "wallet")] #[cfg(feature = "wallet")]
pub mod wallet; pub mod wallet;
#[doc(hidden)]
pub use bitcoin::secp256k1;
#[doc(hidden)]
pub use lightning_invoice::{self, Bolt11Invoice};
#[cfg(feature = "mint")] #[cfg(feature = "mint")]
#[doc(hidden)]
pub use mint::Mint; pub use mint::Mint;
#[cfg(feature = "wallet")] #[cfg(feature = "wallet")]
#[doc(hidden)]
pub use wallet::Wallet; pub use wallet::Wallet;
#[doc(hidden)]
pub use self::amount::Amount; pub use self::amount::Amount;
#[doc(hidden)]
pub use self::url::UncheckedUrl; pub use self::url::UncheckedUrl;
#[doc(hidden)]
pub use self::util::SECP256K1; pub use self::util::SECP256K1;
#[cfg(feature = "wallet")] #[cfg(feature = "wallet")]
#[doc(hidden)]
pub use self::wallet::client::HttpClient; pub use self::wallet::client::HttpClient;
/// Result
#[doc(hidden)]
pub type Result<T, E = Box<dyn std::error::Error>> = std::result::Result<T, E>; pub type Result<T, E = Box<dyn std::error::Error>> = std::result::Result<T, E>;

View File

@@ -6,6 +6,7 @@ use thiserror::Error;
use crate::cdk_database; use crate::cdk_database;
use crate::error::{ErrorCode, ErrorResponse}; use crate::error::{ErrorCode, ErrorResponse};
/// CDK Mint Error
#[derive(Debug, Error)] #[derive(Debug, Error)]
pub enum Error { pub enum Error {
/// Unknown Keyset /// Unknown Keyset
@@ -14,47 +15,67 @@ pub enum Error {
/// Inactive Keyset /// Inactive Keyset
#[error("Inactive Keyset")] #[error("Inactive Keyset")]
InactiveKeyset, InactiveKeyset,
/// There is not key for amount given
#[error("No key for amount")] #[error("No key for amount")]
AmountKey, AmountKey,
/// Amount is not what is expected
#[error("Amount")] #[error("Amount")]
Amount, Amount,
/// Duplicate proofs provided
#[error("Duplicate proofs")] #[error("Duplicate proofs")]
DuplicateProofs, DuplicateProofs,
/// Token is already spent
#[error("Token Already Spent")] #[error("Token Already Spent")]
TokenAlreadySpent, TokenAlreadySpent,
/// Token is already pending
#[error("Token Pending")] #[error("Token Pending")]
TokenPending, TokenPending,
/// Quote is not paiud
#[error("Quote not paid")] #[error("Quote not paid")]
UnpaidQuote, UnpaidQuote,
/// Quote has already been paid
#[error("Quote is already paid")] #[error("Quote is already paid")]
PaidQuote, PaidQuote,
/// Quote is not known
#[error("Unknown quote")] #[error("Unknown quote")]
UnknownQuote, UnknownQuote,
/// Quote is pending
#[error("Quote pending")] #[error("Quote pending")]
PendingQuote, PendingQuote,
/// ecash already issued for quote
#[error("Quote already issued")] #[error("Quote already issued")]
IssuedQuote, IssuedQuote,
/// Unknown secret kind
#[error("Unknown secret kind")] #[error("Unknown secret kind")]
UnknownSecretKind, UnknownSecretKind,
/// Multiple units provided
#[error("Cannot have multiple units")] #[error("Cannot have multiple units")]
MultipleUnits, MultipleUnits,
/// BlindMessage is already signed
#[error("Blinded Message is already signed")] #[error("Blinded Message is already signed")]
BlindedMessageAlreadySigned, BlindedMessageAlreadySigned,
/// Cashu Error
#[error(transparent)] #[error(transparent)]
Cashu(#[from] crate::error::Error), Cashu(#[from] crate::error::Error),
/// Secret Error
#[error(transparent)] #[error(transparent)]
Secret(#[from] crate::secret::Error), Secret(#[from] crate::secret::Error),
/// NUT00 Error
#[error(transparent)] #[error(transparent)]
NUT00(#[from] crate::nuts::nut00::Error), NUT00(#[from] crate::nuts::nut00::Error),
/// NUT11 Error
#[error(transparent)] #[error(transparent)]
NUT11(#[from] crate::nuts::nut11::Error), NUT11(#[from] crate::nuts::nut11::Error),
/// NUT12 Error
#[error(transparent)] #[error(transparent)]
Nut12(#[from] crate::nuts::nut12::Error), Nut12(#[from] crate::nuts::nut12::Error),
/// NUT14 Error
#[error(transparent)] #[error(transparent)]
Nut14(#[from] crate::nuts::nut14::Error), Nut14(#[from] crate::nuts::nut14::Error),
/// Database Error /// Database Error
#[error(transparent)] #[error(transparent)]
Database(#[from] cdk_database::Error), Database(#[from] cdk_database::Error),
/// Custom Error
#[error("`{0}`")] #[error("`{0}`")]
Custom(String), Custom(String),
} }

View File

@@ -21,18 +21,23 @@ use crate::Amount;
pub mod error; pub mod error;
/// Cashu Mint
#[derive(Clone)] #[derive(Clone)]
pub struct Mint { pub struct Mint {
/// Mint Url
pub mint_url: UncheckedUrl, pub mint_url: UncheckedUrl,
mint_info: MintInfo, mint_info: MintInfo,
keysets: Arc<RwLock<HashMap<Id, MintKeySet>>>, keysets: Arc<RwLock<HashMap<Id, MintKeySet>>>,
secp_ctx: Secp256k1<secp256k1::All>, secp_ctx: Secp256k1<secp256k1::All>,
xpriv: ExtendedPrivKey, xpriv: ExtendedPrivKey,
/// Mint Expected [`FeeReserve`]
pub fee_reserve: FeeReserve, pub fee_reserve: FeeReserve,
/// Mint Storage backend
pub localstore: Arc<dyn MintDatabase<Err = cdk_database::Error> + Send + Sync>, pub localstore: Arc<dyn MintDatabase<Err = cdk_database::Error> + Send + Sync>,
} }
impl Mint { impl Mint {
/// Create new [`Mint`]
pub async fn new( pub async fn new(
mint_url: &str, mint_url: &str,
seed: &[u8], seed: &[u8],
@@ -844,19 +849,30 @@ impl Mint {
/// Mint Fee Reserve /// Mint Fee Reserve
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct FeeReserve { pub struct FeeReserve {
/// Absolute expected min fee
pub min_fee_reserve: Amount, pub min_fee_reserve: Amount,
/// Percentage expected fee
pub percent_fee_reserve: f32, pub percent_fee_reserve: f32,
} }
/// Mint Keyset Info /// Mint Keyset Info
#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
pub struct MintKeySetInfo { pub struct MintKeySetInfo {
/// Keyset [`Id`]
pub id: Id, pub id: Id,
/// Keyset [`CurrencyUnit`]
pub unit: CurrencyUnit, pub unit: CurrencyUnit,
/// Keyset active or inactive
/// Mint will only issue new [`BlindSignature`] on active keysets
pub active: bool, pub active: bool,
/// Starting unix time Keyset is valid from
pub valid_from: u64, 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<u64>, pub valid_to: Option<u64>,
/// [`DerivationPath`] of Keyset
pub derivation_path: DerivationPath, pub derivation_path: DerivationPath,
/// Max order of keyset
pub max_order: u8, pub max_order: u8,
} }

View File

@@ -1,3 +1,7 @@
//! Nuts
//!
//! See all at <https://github.com/cashubtc/nuts>
pub mod nut00; pub mod nut00;
pub mod nut01; pub mod nut01;
pub mod nut02; pub mod nut02;

View File

@@ -30,6 +30,7 @@ use crate::Amount;
/// List of [Proof] /// List of [Proof]
pub type Proofs = Vec<Proof>; pub type Proofs = Vec<Proof>;
/// NUT00 Error
#[derive(Debug, Error)] #[derive(Debug, Error)]
pub enum Error { pub enum Error {
/// Proofs required /// Proofs required
@@ -141,6 +142,7 @@ pub enum Witness {
} }
impl Witness { impl Witness {
/// Add signatures to [`Witness`]
pub fn add_signatures(&mut self, signatues: Vec<String>) { pub fn add_signatures(&mut self, signatues: Vec<String>) {
match self { match self {
Self::P2PKWitness(p2pk_witness) => p2pk_witness.signatures.extend(signatues), Self::P2PKWitness(p2pk_witness) => p2pk_witness.signatures.extend(signatues),
@@ -154,6 +156,7 @@ impl Witness {
} }
} }
/// Get signatures on [`Witness`]
pub fn signatures(&self) -> Option<Vec<String>> { pub fn signatures(&self) -> Option<Vec<String>> {
match self { match self {
Self::P2PKWitness(witness) => Some(witness.signatures.clone()), Self::P2PKWitness(witness) => Some(witness.signatures.clone()),
@@ -161,6 +164,7 @@ impl Witness {
} }
} }
/// Get preimage from [`Witness`]
pub fn preimage(&self) -> Option<String> { pub fn preimage(&self) -> Option<String> {
match self { match self {
Self::P2PKWitness(_witness) => None, Self::P2PKWitness(_witness) => None,
@@ -191,6 +195,7 @@ pub struct Proof {
} }
impl Proof { impl Proof {
/// Create new [`Proof`]
pub fn new(amount: Amount, keyset_id: Id, secret: Secret, c: PublicKey) -> Self { pub fn new(amount: Amount, keyset_id: Id, secret: Secret, c: PublicKey) -> Self {
Proof { Proof {
amount, amount,
@@ -202,6 +207,9 @@ impl Proof {
} }
} }
/// Get y from proof
///
/// Where y is `hash_to_curve(secret)`
pub fn y(&self) -> Result<PublicKey, Error> { pub fn y(&self) -> Result<PublicKey, Error> {
Ok(hash_to_curve(self.secret.as_bytes())?) 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)] #[derive(Debug, Clone, Default, PartialEq, Eq, Hash)]
pub enum CurrencyUnit { pub enum CurrencyUnit {
/// Sat
#[default] #[default]
Sat, Sat,
/// Msat
Msat, Msat,
/// Usd
Usd, Usd,
/// Custom unit
Custom(String), Custom(String),
} }
@@ -278,10 +291,13 @@ impl<'de> Deserialize<'de> for CurrencyUnit {
} }
} }
/// Payment Method
#[derive(Debug, Clone, Default, PartialEq, Eq, Hash)] #[derive(Debug, Clone, Default, PartialEq, Eq, Hash)]
pub enum PaymentMethod { pub enum PaymentMethod {
/// Bolt11 payment type
#[default] #[default]
Bolt11, Bolt11,
/// Custom payment type:
Custom(String), Custom(String),
} }
@@ -325,6 +341,7 @@ impl<'de> Deserialize<'de> for PaymentMethod {
} }
} }
/// PreMint
#[derive(Debug, Clone, PartialEq, Eq, Serialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct PreMint { pub struct PreMint {
/// Blinded message /// Blinded message
@@ -349,8 +366,10 @@ impl PartialOrd for PreMint {
} }
} }
/// Premint Secrets
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize)] #[derive(Debug, Default, Clone, PartialEq, Eq, Serialize)]
pub struct PreMintSecrets { pub struct PreMintSecrets {
/// Secrets
pub secrets: Vec<PreMint>, pub secrets: Vec<PreMint>,
} }
@@ -382,6 +401,7 @@ impl PreMintSecrets {
Ok(PreMintSecrets { secrets: output }) Ok(PreMintSecrets { secrets: output })
} }
/// Outputs from pre defined secrets
pub fn from_secrets( pub fn from_secrets(
keyset_id: Id, keyset_id: Id,
amounts: Vec<Amount>, amounts: Vec<Amount>,
@@ -428,6 +448,7 @@ impl PreMintSecrets {
Ok(PreMintSecrets { secrets: output }) Ok(PreMintSecrets { secrets: output })
} }
/// Outputs with specific spending conditions
pub fn with_conditions( pub fn with_conditions(
keyset_id: Id, keyset_id: Id,
amount: Amount, amount: Amount,
@@ -457,18 +478,25 @@ impl PreMintSecrets {
Ok(PreMintSecrets { secrets: output }) Ok(PreMintSecrets { secrets: output })
} }
/// Iterate over secrets
#[inline]
pub fn iter(&self) -> impl Iterator<Item = &PreMint> { pub fn iter(&self) -> impl Iterator<Item = &PreMint> {
self.secrets.iter() self.secrets.iter()
} }
/// Length of secrets
#[inline]
pub fn len(&self) -> usize { pub fn len(&self) -> usize {
self.secrets.len() self.secrets.len()
} }
/// If secrets is empty
#[inline]
pub fn is_empty(&self) -> bool { pub fn is_empty(&self) -> bool {
self.secrets.is_empty() self.secrets.is_empty()
} }
/// Totoal amount of secrets
pub fn total_amount(&self) -> Amount { pub fn total_amount(&self) -> Amount {
self.secrets self.secrets
.iter() .iter()
@@ -476,26 +504,38 @@ impl PreMintSecrets {
.sum() .sum()
} }
/// [`BlindedMessage`]s from [`PreMintSecrets`]
#[inline]
pub fn blinded_messages(&self) -> Vec<BlindedMessage> { pub fn blinded_messages(&self) -> Vec<BlindedMessage> {
self.iter().map(|pm| pm.blinded_message.clone()).collect() self.iter().map(|pm| pm.blinded_message.clone()).collect()
} }
/// [`Secret`]s from [`PreMintSecrets`]
#[inline]
pub fn secrets(&self) -> Vec<Secret> { pub fn secrets(&self) -> Vec<Secret> {
self.iter().map(|pm| pm.secret.clone()).collect() self.iter().map(|pm| pm.secret.clone()).collect()
} }
/// Blinding factor from [`PreMintSecrets`]
#[inline]
pub fn rs(&self) -> Vec<SecretKey> { pub fn rs(&self) -> Vec<SecretKey> {
self.iter().map(|pm| pm.r.clone()).collect() self.iter().map(|pm| pm.r.clone()).collect()
} }
/// Amounts from [`PreMintSecrets`]
#[inline]
pub fn amounts(&self) -> Vec<Amount> { pub fn amounts(&self) -> Vec<Amount> {
self.iter().map(|pm| pm.amount).collect() self.iter().map(|pm| pm.amount).collect()
} }
/// Combine [`PreMintSecrets`]
#[inline]
pub fn combine(&mut self, mut other: Self) { pub fn combine(&mut self, mut other: Self) {
self.secrets.append(&mut other.secrets) self.secrets.append(&mut other.secrets)
} }
/// Sort [`PreMintSecrets`] by [`Amount`]
#[inline]
pub fn sort_secrets(&mut self) { pub fn sort_secrets(&mut self) {
self.secrets.sort(); self.secrets.sort();
} }
@@ -523,8 +563,10 @@ impl PartialOrd for PreMintSecrets {
} }
} }
/// Token
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Token { pub struct Token {
/// Proofs in [`Token`] by mint
pub token: Vec<MintProofs>, pub token: Vec<MintProofs>,
/// Memo for token /// Memo for token
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
@@ -535,6 +577,7 @@ pub struct Token {
} }
impl Token { impl Token {
/// Create new [`Token`]
pub fn new( pub fn new(
mint_url: UncheckedUrl, mint_url: UncheckedUrl,
proofs: Proofs, proofs: Proofs,
@@ -555,6 +598,8 @@ impl Token {
}) })
} }
/// Token Info
/// Assumes only one mint in [`Token`]
pub fn token_info(&self) -> (Amount, String) { pub fn token_info(&self) -> (Amount, String) {
let mut amount = Amount::ZERO; let mut amount = Amount::ZERO;
@@ -595,13 +640,17 @@ impl fmt::Display for Token {
} }
} }
/// Mint Proofs
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct MintProofs { pub struct MintProofs {
/// Url of mint
pub mint: UncheckedUrl, pub mint: UncheckedUrl,
/// [`Proofs`]
pub proofs: Proofs, pub proofs: Proofs,
} }
impl MintProofs { impl MintProofs {
/// Create new [`MintProofs`]
pub fn new(mint_url: UncheckedUrl, proofs: Proofs) -> Self { pub fn new(mint_url: UncheckedUrl, proofs: Proofs) -> Self {
Self { Self {
mint: mint_url, mint: mint_url,

View File

@@ -18,17 +18,23 @@ pub use self::secret_key::SecretKey;
use super::nut02::KeySet; use super::nut02::KeySet;
use crate::amount::Amount; use crate::amount::Amount;
/// Nut01 Error
#[derive(Debug, Error)] #[derive(Debug, Error)]
pub enum Error { pub enum Error {
/// Secp256k1 Error
#[error(transparent)] #[error(transparent)]
Secp256k1(#[from] secp256k1::Error), Secp256k1(#[from] secp256k1::Error),
/// Json Error
#[error(transparent)] #[error(transparent)]
Json(#[from] serde_json::Error), Json(#[from] serde_json::Error),
#[cfg(feature = "nostr")] /// Invalid Pubkey size
#[error(transparent)]
NostrKey(#[from] nostr_sdk::key::Error),
#[error("Invalid public key size: expected={expected}, found={found}")] #[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] /// Mint Keys [NUT-01]
@@ -47,16 +53,19 @@ impl From<MintKeys> for Keys {
} }
impl Keys { impl Keys {
/// Create new [`Keys`]
#[inline] #[inline]
pub fn new(keys: BTreeMap<String, PublicKey>) -> Self { pub fn new(keys: BTreeMap<String, PublicKey>) -> Self {
Self(keys) Self(keys)
} }
/// Get [`Keys`]
#[inline] #[inline]
pub fn keys(&self) -> &BTreeMap<String, PublicKey> { pub fn keys(&self) -> &BTreeMap<String, PublicKey> {
&self.0 &self.0
} }
/// Get [`PublicKey`] for [`Amount`]
#[inline] #[inline]
pub fn amount_key(&self, amount: Amount) -> Option<PublicKey> { pub fn amount_key(&self, amount: Amount) -> Option<PublicKey> {
self.0.get(&amount.to_string()).copied() self.0.get(&amount.to_string()).copied()
@@ -72,6 +81,7 @@ impl Keys {
/// Mint Public Keys [NUT-01] /// Mint Public Keys [NUT-01]
#[derive(Debug, Clone, PartialEq, Eq, Serialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct KeysResponse { pub struct KeysResponse {
/// Keysets
pub keysets: Vec<KeySet>, pub keysets: Vec<KeySet>,
} }
@@ -116,19 +126,24 @@ impl DerefMut for MintKeys {
} }
impl MintKeys { impl MintKeys {
/// Create new [`MintKeys`]
#[inline] #[inline]
pub fn new(map: BTreeMap<Amount, MintKeyPair>) -> Self { pub fn new(map: BTreeMap<Amount, MintKeyPair>) -> Self {
Self(map) Self(map)
} }
} }
/// Mint Public Private key pair
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct MintKeyPair { pub struct MintKeyPair {
/// Publickey
pub public_key: PublicKey, pub public_key: PublicKey,
/// Secretkey
pub secret_key: SecretKey, pub secret_key: SecretKey,
} }
impl MintKeyPair { impl MintKeyPair {
/// [`MintKeyPair`] from secret key
#[inline] #[inline]
pub fn from_secret_key(secret_key: SecretKey) -> Self { pub fn from_secret_key(secret_key: SecretKey) -> Self {
Self { Self {

View File

@@ -11,6 +11,7 @@ use serde::{Deserialize, Deserializer, Serialize};
use super::Error; use super::Error;
use crate::SECP256K1; use crate::SECP256K1;
/// PublicKey
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct PublicKey { pub struct PublicKey {
inner: secp256k1::PublicKey, inner: secp256k1::PublicKey,
@@ -30,38 +31,6 @@ impl From<secp256k1::PublicKey> for PublicKey {
} }
} }
#[cfg(feature = "nostr")]
impl TryFrom<PublicKey> for nostr_sdk::PublicKey {
type Error = Error;
fn try_from(pubkey: PublicKey) -> Result<Self, Self::Error> {
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<Self, Self::Error> {
Ok(nostr_sdk::PublicKey::from_slice(&pubkey.to_bytes())?)
}
}
#[cfg(feature = "nostr")]
impl TryFrom<nostr_sdk::PublicKey> for PublicKey {
type Error = Error;
fn try_from(pubkey: nostr_sdk::PublicKey) -> Result<Self, Self::Error> {
(&pubkey).try_into()
}
}
#[cfg(feature = "nostr")]
impl TryFrom<&nostr_sdk::PublicKey> for PublicKey {
type Error = Error;
fn try_from(pubkey: &nostr_sdk::PublicKey) -> Result<Self, Self::Error> {
PublicKey::from_slice(&pubkey.to_bytes())
}
}
impl PublicKey { impl PublicKey {
/// Parse from `bytes` /// Parse from `bytes`
#[inline] #[inline]
@@ -92,16 +61,19 @@ impl PublicKey {
}) })
} }
/// [`PublicKey`] to bytes
#[inline] #[inline]
pub fn to_bytes(&self) -> [u8; 33] { pub fn to_bytes(&self) -> [u8; 33] {
self.inner.serialize() self.inner.serialize()
} }
/// To uncompressed bytes
#[inline] #[inline]
pub fn to_uncompressed_bytes(&self) -> [u8; 65] { pub fn to_uncompressed_bytes(&self) -> [u8; 65] {
self.inner.serialize_uncompressed() self.inner.serialize_uncompressed()
} }
/// To [`XOnlyPublicKey`]
#[inline] #[inline]
pub fn x_only_public_key(&self) -> XOnlyPublicKey { pub fn x_only_public_key(&self) -> XOnlyPublicKey {
self.inner.x_only_public_key().0 self.inner.x_only_public_key().0

View File

@@ -13,6 +13,7 @@ use serde::{Deserialize, Deserializer, Serialize};
use super::{Error, PublicKey}; use super::{Error, PublicKey};
use crate::SECP256K1; use crate::SECP256K1;
/// SecretKey
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub struct SecretKey { pub struct SecretKey {
inner: secp256k1::SecretKey, inner: secp256k1::SecretKey,
@@ -32,22 +33,6 @@ impl From<secp256k1::SecretKey> for SecretKey {
} }
} }
#[cfg(feature = "nostr")]
impl TryFrom<SecretKey> for nostr_sdk::SecretKey {
type Error = Error;
fn try_from(seckey: SecretKey) -> Result<Self, Self::Error> {
(&seckey).try_into()
}
}
#[cfg(feature = "nostr")]
impl TryFrom<&SecretKey> for nostr_sdk::SecretKey {
type Error = Error;
fn try_from(seckey: &SecretKey) -> Result<Self, Self::Error> {
Ok(nostr_sdk::SecretKey::from_slice(&seckey.to_secret_bytes())?)
}
}
impl fmt::Display for SecretKey { impl fmt::Display for SecretKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.to_secret_hex()) write!(f, "{}", self.to_secret_hex())
@@ -105,11 +90,13 @@ impl SecretKey {
self.inner.public_key(&SECP256K1).into() self.inner.public_key(&SECP256K1).into()
} }
/// [`SecretKey`] to [`Scalar`]
#[inline] #[inline]
pub fn to_scalar(self) -> Scalar { pub fn to_scalar(self) -> Scalar {
Scalar::from(self.inner) Scalar::from(self.inner)
} }
/// [`SecretKey`] as [`Scalar`]
#[inline] #[inline]
pub fn as_scalar(&self) -> Scalar { pub fn as_scalar(&self) -> Scalar {
Scalar::from(self.inner) Scalar::from(self.inner)

View File

@@ -30,30 +30,39 @@ use crate::util::hex;
#[cfg(feature = "mint")] #[cfg(feature = "mint")]
use crate::Amount; use crate::Amount;
/// NUT02 Error
#[derive(Debug, Error)] #[derive(Debug, Error)]
pub enum Error { pub enum Error {
/// Hex Error
#[error(transparent)] #[error(transparent)]
HexError(#[from] hex::Error), HexError(#[from] hex::Error),
/// Keyset length error
#[error("NUT02: ID length invalid")] #[error("NUT02: ID length invalid")]
Length, Length,
/// Unknown version
#[error("NUT02: Unknown Version")] #[error("NUT02: Unknown Version")]
UnknownVersion, UnknownVersion,
/// Slice Error
#[error(transparent)] #[error(transparent)]
Slice(#[from] TryFromSliceError), Slice(#[from] TryFromSliceError),
} }
/// Keyset version
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum KeySetVersion { pub enum KeySetVersion {
/// Current Version 00
Version00, Version00,
} }
impl KeySetVersion { impl KeySetVersion {
/// [`KeySetVersion`] to byte
pub fn to_byte(&self) -> u8 { pub fn to_byte(&self) -> u8 {
match self { match self {
Self::Version00 => 0, Self::Version00 => 0,
} }
} }
/// [`KeySetVersion`] from byte
pub fn from_byte(byte: &u8) -> Result<Self, Error> { pub fn from_byte(byte: &u8) -> Result<Self, Error> {
match byte { match byte {
0 => Ok(Self::Version00), 0 => Ok(Self::Version00),
@@ -84,10 +93,12 @@ impl Id {
const STRLEN: usize = 14; const STRLEN: usize = 14;
const BYTELEN: usize = 7; const BYTELEN: usize = 7;
/// [`Id`] to bytes
pub fn to_bytes(&self) -> Vec<u8> { pub fn to_bytes(&self) -> Vec<u8> {
[vec![self.version.to_byte()], self.id.to_vec()].concat() [vec![self.version.to_byte()], self.id.to_vec()].concat()
} }
/// [`Id`] from bytes
pub fn from_bytes(bytes: &[u8]) -> Result<Self, Error> { pub fn from_bytes(bytes: &[u8]) -> Result<Self, Error> {
Ok(Self { Ok(Self {
version: KeySetVersion::from_byte(&bytes[0])?, version: KeySetVersion::from_byte(&bytes[0])?,
@@ -220,6 +231,7 @@ pub struct KeysetResponse {
} }
impl KeysetResponse { impl KeysetResponse {
/// Create new [`KeysetResponse`]
pub fn new(keysets: Vec<KeySet>) -> Self { pub fn new(keysets: Vec<KeySet>) -> Self {
Self { Self {
keysets: keysets.into_iter().map(|keyset| keyset.into()).collect(), keysets: keysets.into_iter().map(|keyset| keyset.into()).collect(),
@@ -227,10 +239,14 @@ impl KeysetResponse {
} }
} }
/// Keyset
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)] #[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
pub struct KeySet { pub struct KeySet {
/// Keyset [`Id`]
pub id: Id, pub id: Id,
/// Keyset [`CurrencyUnit`]
pub unit: CurrencyUnit, pub unit: CurrencyUnit,
/// Keyset [`Keys`]
pub keys: Keys, pub keys: Keys,
} }
@@ -245,10 +261,15 @@ impl From<MintKeySet> for KeySet {
} }
} }
/// KeySetInfo
#[derive(Debug, Clone, Hash, PartialEq, Eq, Deserialize, Serialize)] #[derive(Debug, Clone, Hash, PartialEq, Eq, Deserialize, Serialize)]
pub struct KeySetInfo { pub struct KeySetInfo {
/// Keyset [`Id`]
pub id: Id, pub id: Id,
/// Keyset [`CurrencyUnit`]
pub unit: CurrencyUnit, pub unit: CurrencyUnit,
/// Keyset state
/// Mint will only sign from an active keyset
pub active: bool, pub active: bool,
} }
@@ -262,16 +283,21 @@ impl From<KeySet> for KeySetInfo {
} }
} }
/// MintKeyset
#[cfg(feature = "mint")] #[cfg(feature = "mint")]
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct MintKeySet { pub struct MintKeySet {
/// Keyset [`Id`]
pub id: Id, pub id: Id,
/// Keyset [`CurrencyUnit`]
pub unit: CurrencyUnit, pub unit: CurrencyUnit,
/// Keyset [`MintKeys`]
pub keys: MintKeys, pub keys: MintKeys,
} }
#[cfg(feature = "mint")] #[cfg(feature = "mint")]
impl MintKeySet { impl MintKeySet {
/// Generate new [`MintKeySet`]
pub fn generate<C: secp256k1::Signing>( pub fn generate<C: secp256k1::Signing>(
secp: &Secp256k1<C>, secp: &Secp256k1<C>,
xpriv: ExtendedPrivKey, xpriv: ExtendedPrivKey,
@@ -306,6 +332,7 @@ impl MintKeySet {
} }
} }
/// Generate new [`MintKeySet`] from seed
pub fn generate_from_seed<C: secp256k1::Signing>( pub fn generate_from_seed<C: secp256k1::Signing>(
secp: &Secp256k1<C>, secp: &Secp256k1<C>,
seed: &[u8], seed: &[u8],
@@ -325,6 +352,7 @@ impl MintKeySet {
) )
} }
/// Generate new [`MintKeySet`] from xpriv
pub fn generate_from_xpriv<C: secp256k1::Signing>( pub fn generate_from_xpriv<C: secp256k1::Signing>(
secp: &Secp256k1<C>, secp: &Secp256k1<C>,
xpriv: ExtendedPrivKey, xpriv: ExtendedPrivKey,

View File

@@ -7,9 +7,12 @@ use serde::{Deserialize, Serialize};
use super::nut00::{BlindSignature, BlindedMessage, PreMintSecrets, Proofs}; use super::nut00::{BlindSignature, BlindedMessage, PreMintSecrets, Proofs};
use crate::Amount; use crate::Amount;
/// Preswap information
#[derive(Debug, Clone, PartialEq, Eq, Serialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct PreSwap { pub struct PreSwap {
/// Preswap mint secrets
pub pre_mint_secrets: PreMintSecrets, pub pre_mint_secrets: PreMintSecrets,
/// Swap request
pub swap_request: SwapRequest, pub swap_request: SwapRequest,
} }
@@ -23,16 +26,17 @@ pub struct SwapRequest {
} }
impl SwapRequest { impl SwapRequest {
/// Create new [`SwapRequest`]
pub fn new(inputs: Proofs, outputs: Vec<BlindedMessage>) -> Self { pub fn new(inputs: Proofs, outputs: Vec<BlindedMessage>) -> Self {
Self { inputs, outputs } Self { inputs, outputs }
} }
/// Total value of proofs in `SplitRequest` /// Total value of proofs in [`SwapRequest`]
pub fn input_amount(&self) -> Amount { pub fn input_amount(&self) -> Amount {
self.inputs.iter().map(|proof| proof.amount).sum() 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 { pub fn output_amount(&self) -> Amount {
self.outputs.iter().map(|proof| proof.amount).sum() self.outputs.iter().map(|proof| proof.amount).sum()
} }
@@ -46,12 +50,14 @@ pub struct SwapResponse {
} }
impl SwapResponse { impl SwapResponse {
/// Create new [`SwapRequest`]
pub fn new(promises: Vec<BlindSignature>) -> SwapResponse { pub fn new(promises: Vec<BlindSignature>) -> SwapResponse {
SwapResponse { SwapResponse {
signatures: promises, signatures: promises,
} }
} }
/// Total [`Amount`] of promises
pub fn promises_amount(&self) -> Amount { pub fn promises_amount(&self) -> Amount {
self.signatures self.signatures
.iter() .iter()

View File

@@ -14,6 +14,7 @@ use super::MintQuoteState;
use crate::types::MintQuote; use crate::types::MintQuote;
use crate::Amount; use crate::Amount;
/// NUT04 Error
#[derive(Debug, Error)] #[derive(Debug, Error)]
pub enum Error { pub enum Error {
/// Unknown Quote State /// Unknown Quote State
@@ -34,10 +35,14 @@ pub struct MintQuoteBolt11Request {
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Default, Serialize, Deserialize)] #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Default, Serialize, Deserialize)]
#[serde(rename_all = "UPPERCASE")] #[serde(rename_all = "UPPERCASE")]
pub enum QuoteState { pub enum QuoteState {
/// Quote has not been paid
#[default] #[default]
Unpaid, Unpaid,
/// Quote has been paid and wallet can mint
Paid, Paid,
/// Minting is in progress
Pending, Pending,
/// ecash issued for quote
Issued, Issued,
} }
@@ -171,6 +176,7 @@ pub struct MintBolt11Request {
} }
impl MintBolt11Request { impl MintBolt11Request {
/// Total [`Amount`] of outputs
pub fn total_amount(&self) -> Amount { pub fn total_amount(&self) -> Amount {
self.outputs self.outputs
.iter() .iter()

View File

@@ -14,6 +14,7 @@ use super::nut15::Mpp;
use crate::types::MeltQuote; use crate::types::MeltQuote;
use crate::{Amount, Bolt11Invoice}; use crate::{Amount, Bolt11Invoice};
/// NUT05 Error
#[derive(Debug, Error)] #[derive(Debug, Error)]
pub enum Error { pub enum Error {
/// Unknown Quote State /// Unknown Quote State
@@ -36,9 +37,12 @@ pub struct MeltQuoteBolt11Request {
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Default, Serialize, Deserialize)] #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Default, Serialize, Deserialize)]
#[serde(rename_all = "UPPERCASE")] #[serde(rename_all = "UPPERCASE")]
pub enum QuoteState { pub enum QuoteState {
/// Quote has not been paid
#[default] #[default]
Unpaid, Unpaid,
/// Quote has been paid
Paid, Paid,
/// Paying quote is in progress
Pending, Pending,
} }
@@ -203,6 +207,7 @@ pub struct MeltBolt11Request {
} }
impl MeltBolt11Request { impl MeltBolt11Request {
/// Total [`Amount`] of [`Proofs`]
pub fn proofs_amount(&self) -> Amount { pub fn proofs_amount(&self) -> Amount {
self.inputs.iter().map(|proof| proof.amount).sum() self.inputs.iter().map(|proof| proof.amount).sum()
} }

View File

@@ -13,7 +13,9 @@ use super::{nut04, nut05, nut15};
/// Mint Version /// Mint Version
#[derive(Debug, Clone, PartialEq, Eq, Hash)] #[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct MintVersion { pub struct MintVersion {
/// Mint Software name
pub name: String, pub name: String,
/// Mint Version
pub version: String, pub version: String,
} }
@@ -76,36 +78,47 @@ pub struct MintInfo {
/// Supported nuts and settings /// Supported nuts and settings
#[derive(Debug, Default, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] #[derive(Debug, Default, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct Nuts { pub struct Nuts {
/// NUT04 Settings
#[serde(default)] #[serde(default)]
#[serde(rename = "4")] #[serde(rename = "4")]
pub nut04: nut04::Settings, pub nut04: nut04::Settings,
/// NUT05 Settings
#[serde(default)] #[serde(default)]
#[serde(rename = "5")] #[serde(rename = "5")]
pub nut05: nut05::Settings, pub nut05: nut05::Settings,
/// NUT07 Settings
#[serde(default)] #[serde(default)]
#[serde(rename = "7")] #[serde(rename = "7")]
pub nut07: SupportedSettings, pub nut07: SupportedSettings,
/// NUT08 Settings
#[serde(default)] #[serde(default)]
#[serde(rename = "8")] #[serde(rename = "8")]
pub nut08: SupportedSettings, pub nut08: SupportedSettings,
/// NUT09 Settings
#[serde(default)] #[serde(default)]
#[serde(rename = "9")] #[serde(rename = "9")]
pub nut09: SupportedSettings, pub nut09: SupportedSettings,
/// NUT10 Settings
#[serde(rename = "10")] #[serde(rename = "10")]
#[serde(default)] #[serde(default)]
pub nut10: SupportedSettings, pub nut10: SupportedSettings,
/// NUT11 Settings
#[serde(rename = "11")] #[serde(rename = "11")]
#[serde(default)] #[serde(default)]
pub nut11: SupportedSettings, pub nut11: SupportedSettings,
/// NUT12 Settings
#[serde(default)] #[serde(default)]
#[serde(rename = "12")] #[serde(rename = "12")]
pub nut12: SupportedSettings, pub nut12: SupportedSettings,
/// NUT13 Settings
#[serde(default)] #[serde(default)]
#[serde(rename = "13")] #[serde(rename = "13")]
pub nut13: SupportedSettings, pub nut13: SupportedSettings,
/// NUT14 Settings
#[serde(default)] #[serde(default)]
#[serde(rename = "14")] #[serde(rename = "14")]
pub nut14: SupportedSettings, pub nut14: SupportedSettings,
/// NUT15 Settings
#[serde(default)] #[serde(default)]
#[serde(rename = "15")] #[serde(rename = "15")]
pub nut15: nut15::MppMethodSettings, pub nut15: nut15::MppMethodSettings,

View File

@@ -18,12 +18,21 @@ pub enum Error {
UnknownState, UnknownState,
} }
/// State of Proof
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "UPPERCASE")] #[serde(rename_all = "UPPERCASE")]
pub enum State { pub enum State {
/// Spent
Spent, Spent,
/// Unspent
Unspent, Unspent,
/// Pending
///
/// Currently being used in a transaction i.e. melt in progress
Pending, Pending,
/// Proof is reserved
///
/// i.e. used to create a token
Reserved, Reserved,
} }
@@ -77,5 +86,6 @@ pub struct ProofState {
/// Check Spendable Response [NUT-07] /// Check Spendable Response [NUT-07]
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CheckStateResponse { pub struct CheckStateResponse {
/// Proof states
pub states: Vec<ProofState>, pub states: Vec<ProofState>,
} }

View File

@@ -6,6 +6,7 @@ use super::nut05::{MeltBolt11Request, MeltQuoteBolt11Response};
use crate::Amount; use crate::Amount;
impl MeltBolt11Request { impl MeltBolt11Request {
/// Total output [`Amount`]
pub fn output_amount(&self) -> Option<Amount> { pub fn output_amount(&self) -> Option<Amount> {
self.outputs self.outputs
.as_ref() .as_ref()
@@ -14,6 +15,7 @@ impl MeltBolt11Request {
} }
impl MeltQuoteBolt11Response { impl MeltQuoteBolt11Response {
/// Total change [`Amount`]
pub fn change_amount(&self) -> Option<Amount> { pub fn change_amount(&self) -> Option<Amount> {
self.change self.change
.as_ref() .as_ref()

View File

@@ -18,6 +18,7 @@ pub enum Kind {
HTLC, HTLC,
} }
/// Secert Date
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct SecretData { pub struct SecretData {
/// Unique random string /// Unique random string
@@ -29,6 +30,7 @@ pub struct SecretData {
pub tags: Option<Vec<Vec<String>>>, pub tags: Option<Vec<Vec<String>>>,
} }
/// NUT10 Secret
#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize)]
pub struct Secret { pub struct Secret {
/// Kind of the spending condition /// Kind of the spending condition
@@ -38,6 +40,7 @@ pub struct Secret {
} }
impl Secret { impl Secret {
/// Create new [`Secret`]
pub fn new<S, V>(kind: Kind, data: S, tags: Option<V>) -> Self pub fn new<S, V>(kind: Kind, data: S, tags: Option<V>) -> Self
where where
S: Into<String>, S: Into<String>,

View File

@@ -23,6 +23,7 @@ use crate::util::{hex, unix_time};
pub mod serde_p2pk_witness; pub mod serde_p2pk_witness;
/// Nut11 Error
#[derive(Debug, Error)] #[derive(Debug, Error)]
pub enum Error { pub enum Error {
/// Incorrect secret kind /// Incorrect secret kind
@@ -84,11 +85,13 @@ pub enum Error {
/// P2Pk Witness /// P2Pk Witness
#[derive(Default, Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] #[derive(Default, Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct P2PKWitness { pub struct P2PKWitness {
/// Signatures
pub signatures: Vec<String>, pub signatures: Vec<String>,
} }
impl P2PKWitness { impl P2PKWitness {
#[inline] #[inline]
/// Check id Witness is empty
pub fn is_empty(&self) -> bool { pub fn is_empty(&self) -> bool {
self.signatures.is_empty() 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)] #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum SpendingConditions { pub enum SpendingConditions {
/// NUT11 Spending conditions /// NUT11 Spending conditions
///
/// Defined in [NUT11](https://github.com/cashubtc/nuts/blob/main/11.md)
P2PKConditions { P2PKConditions {
/// The public key of the recipient of the locked ecash
data: PublicKey, data: PublicKey,
/// Additional Optional Spending [`Conditions`]
conditions: Option<Conditions>, conditions: Option<Conditions>,
}, },
/// NUT14 Spending conditions /// NUT14 Spending conditions
///
/// Dedined in [NUT14](https://github.com/cashubtc/nuts/blob/main/14.md)
HTLCConditions { HTLCConditions {
/// Hash Lock of ecash
data: Sha256Hash, data: Sha256Hash,
/// Additional Optional Spending [`Conditions`]
conditions: Option<Conditions>, conditions: Option<Conditions>,
}, },
} }
@@ -291,6 +305,7 @@ impl SpendingConditions {
} }
} }
/// Number if signatures required to unlock
pub fn num_sigs(&self) -> Option<u64> { pub fn num_sigs(&self) -> Option<u64> {
match self { match self {
Self::P2PKConditions { conditions, .. } => conditions.as_ref().and_then(|c| c.num_sigs), 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<Vec<PublicKey>> { pub fn pubkeys(&self) -> Option<Vec<PublicKey>> {
match self { match self {
Self::P2PKConditions { data, conditions } => { Self::P2PKConditions { data, conditions } => {
@@ -312,6 +328,7 @@ impl SpendingConditions {
} }
} }
/// Locktime of Spending Conditions
pub fn locktime(&self) -> Option<u64> { pub fn locktime(&self) -> Option<u64> {
match self { match self {
Self::P2PKConditions { conditions, .. } => conditions.as_ref().and_then(|c| c.locktime), 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<Vec<PublicKey>> { pub fn refund_keys(&self) -> Option<Vec<PublicKey>> {
match self { match self {
Self::P2PKConditions { conditions, .. } => { Self::P2PKConditions { conditions, .. } => {
@@ -373,18 +391,28 @@ impl From<SpendingConditions> for super::nut10::Secret {
/// P2PK and HTLC spending conditions /// P2PK and HTLC spending conditions
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
pub struct Conditions { pub struct Conditions {
/// Unix locktime after which refund keys can be used
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub locktime: Option<u64>, pub locktime: Option<u64>,
/// Additional Public keys
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub pubkeys: Option<Vec<PublicKey>>, pub pubkeys: Option<Vec<PublicKey>>,
/// Refund keys
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub refund_keys: Option<Vec<PublicKey>>, pub refund_keys: Option<Vec<PublicKey>>,
/// Numbedr of signatures required
///
/// Default is 1
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub num_sigs: Option<u64>, pub num_sigs: Option<u64>,
/// Signature flag
///
/// Default [`SigFlag::SigInputs`]
pub sig_flag: SigFlag, pub sig_flag: SigFlag,
} }
impl Conditions { impl Conditions {
/// Create new Spending [`Conditions`]
pub fn new( pub fn new(
locktime: Option<u64>, locktime: Option<u64>,
pubkeys: Option<Vec<PublicKey>>, pubkeys: Option<Vec<PublicKey>>,
@@ -499,7 +527,7 @@ impl TryFrom<Vec<Vec<String>>> for Conditions {
} }
} }
// P2PK and HTLC Spending condition tags /// P2PK and HTLC Spending condition tags
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, PartialOrd, Ord)] #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, PartialOrd, Ord)]
#[serde(rename_all = "lowercase")] #[serde(rename_all = "lowercase")]
pub enum TagKind { 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)] #[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize, PartialOrd, Ord, Hash)]
pub enum SigFlag { pub enum SigFlag {
#[default] #[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, SigInputs,
/// Requires valid signatures on all inputs and on all outputs.
SigAll, 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<PublicKey>) { pub fn enforce_sig_flag(proofs: Proofs) -> (SigFlag, HashSet<PublicKey>) {
let mut sig_flag = SigFlag::SigInputs; let mut sig_flag = SigFlag::SigInputs;
let mut pubkeys = HashSet::new(); let mut pubkeys = HashSet::new();
@@ -602,16 +637,23 @@ pub fn enforce_sig_flag(proofs: Proofs) -> (SigFlag, HashSet<PublicKey>) {
(sig_flag, pubkeys) (sig_flag, pubkeys)
} }
/// Tag
#[derive(Debug, Clone, Hash, PartialEq, Eq)] #[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub enum Tag { pub enum Tag {
/// Sigflag [`Tag`]
SigFlag(SigFlag), SigFlag(SigFlag),
/// Number of Sigs [`Tag`]
NSigs(u64), NSigs(u64),
/// Locktime [`Tag`]
LockTime(u64), LockTime(u64),
/// Refund [`Tag`]
Refund(Vec<PublicKey>), Refund(Vec<PublicKey>),
/// Pubkeys [`Tag`]
PubKeys(Vec<PublicKey>), PubKeys(Vec<PublicKey>),
} }
impl Tag { impl Tag {
/// Get [`Tag`] Kind
pub fn kind(&self) -> TagKind { pub fn kind(&self) -> TagKind {
match self { match self {
Self::SigFlag(_) => TagKind::SigFlag, Self::SigFlag(_) => TagKind::SigFlag,

View File

@@ -14,36 +14,55 @@ use super::nut02::Id;
use crate::dhke::{hash_e, hash_to_curve}; use crate::dhke::{hash_e, hash_to_curve};
use crate::{Amount, SECP256K1}; use crate::{Amount, SECP256K1};
/// NUT12 Error
#[derive(Debug, Error)] #[derive(Debug, Error)]
pub enum Error { pub enum Error {
/// Missing Dleq Proof
#[error("No Dleq Proof provided")] #[error("No Dleq Proof provided")]
MissingDleqProof, MissingDleqProof,
/// Incomplete Dleq Proof
#[error("Incomplete DLEQ Proof")] #[error("Incomplete DLEQ Proof")]
IncompleteDleqProof, IncompleteDleqProof,
/// Invalid Dleq Proof
#[error("Invalid Dleq Prood")] #[error("Invalid Dleq Prood")]
InvalidDleqProof, InvalidDleqProof,
/// Cashu Error
#[error(transparent)] #[error(transparent)]
Cashu(#[from] crate::error::Error), Cashu(#[from] crate::error::Error),
/// NUT01 Error
#[error(transparent)] #[error(transparent)]
NUT01(#[from] crate::nuts::nut01::Error), NUT01(#[from] crate::nuts::nut01::Error),
/// SECP256k1 Error
#[error(transparent)] #[error(transparent)]
Secp256k1(#[from] secp256k1::Error), 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)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct BlindSignatureDleq { pub struct BlindSignatureDleq {
/// e
pub e: SecretKey, pub e: SecretKey,
/// s
pub s: SecretKey, pub s: SecretKey,
} }
/// Proof Dleq
///
/// Defined in [NUT12](https://github.com/cashubtc/nuts/blob/main/12.md)
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ProofDleq { pub struct ProofDleq {
/// e
pub e: SecretKey, pub e: SecretKey,
/// s
pub s: SecretKey, pub s: SecretKey,
/// Blinding factor
pub r: SecretKey, pub r: SecretKey,
} }
impl ProofDleq { impl ProofDleq {
/// Create new [`ProofDleq`]
pub fn new(e: SecretKey, s: SecretKey, r: SecretKey) -> Self { pub fn new(e: SecretKey, s: SecretKey, r: SecretKey) -> Self {
Self { e, s, r } Self { e, s, r }
} }
@@ -119,6 +138,7 @@ fn calculate_dleq(
} }
impl Proof { impl Proof {
/// Verify proof Dleq
pub fn verify_dleq(&self, mint_pubkey: PublicKey) -> Result<(), Error> { pub fn verify_dleq(&self, mint_pubkey: PublicKey) -> Result<(), Error> {
match &self.dleq { match &self.dleq {
Some(dleq) => { Some(dleq) => {
@@ -165,6 +185,7 @@ impl BlindSignature {
}) })
} }
/// Verify dleq on proof
#[inline] #[inline]
pub fn verify_dleq( pub fn verify_dleq(
&self, &self,
@@ -177,6 +198,7 @@ impl BlindSignature {
} }
} }
/// Add Dleq to proof
/* /*
r = random nonce r = random nonce
R1 = r*G R1 = r*G

View File

@@ -15,6 +15,7 @@ use crate::util::hex;
use crate::{Amount, SECP256K1}; use crate::{Amount, SECP256K1};
impl Secret { impl Secret {
/// Create new [`Secret`] from xpriv
pub fn from_xpriv(xpriv: ExtendedPrivKey, keyset_id: Id, counter: u32) -> Result<Self, Error> { pub fn from_xpriv(xpriv: ExtendedPrivKey, keyset_id: Id, counter: u32) -> Result<Self, Error> {
let path = derive_path_from_keyset_id(keyset_id)? let path = derive_path_from_keyset_id(keyset_id)?
.child(ChildNumber::from_hardened_idx(counter)?) .child(ChildNumber::from_hardened_idx(counter)?)
@@ -28,6 +29,7 @@ impl Secret {
} }
impl SecretKey { impl SecretKey {
/// Create new [`SecretKey`] from xpriv
pub fn from_xpriv(xpriv: ExtendedPrivKey, keyset_id: Id, counter: u32) -> Result<Self, Error> { pub fn from_xpriv(xpriv: ExtendedPrivKey, keyset_id: Id, counter: u32) -> Result<Self, Error> {
let path = derive_path_from_keyset_id(keyset_id)? let path = derive_path_from_keyset_id(keyset_id)?
.child(ChildNumber::from_hardened_idx(counter)?) .child(ChildNumber::from_hardened_idx(counter)?)
@@ -74,6 +76,7 @@ impl PreMintSecrets {
Ok(pre_mint_secrets) Ok(pre_mint_secrets)
} }
/// New [`PreMintSecrets`] from xpriv with a zero amount used for change
pub fn from_xpriv_blank( pub fn from_xpriv_blank(
keyset_id: Id, keyset_id: Id,
counter: u32, counter: u32,

View File

@@ -18,6 +18,7 @@ use crate::util::unix_time;
pub mod serde_htlc_witness; pub mod serde_htlc_witness;
/// NUT14 Errors
#[derive(Debug, Error)] #[derive(Debug, Error)]
pub enum Error { pub enum Error {
/// Incorrect secret kind /// Incorrect secret kind
@@ -42,12 +43,16 @@ pub enum Error {
#[error(transparent)] #[error(transparent)]
NUT11(#[from] super::nut11::Error), NUT11(#[from] super::nut11::Error),
#[error(transparent)] #[error(transparent)]
/// Serde Error
Serde(#[from] serde_json::Error), Serde(#[from] serde_json::Error),
} }
/// HTLC Witness
#[derive(Default, Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] #[derive(Default, Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct HTLCWitness { pub struct HTLCWitness {
/// Primage
pub preimage: String, pub preimage: String,
/// Signatures
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub signatures: Option<Vec<String>>, pub signatures: Option<Vec<String>>,
} }
@@ -125,6 +130,7 @@ impl Proof {
Ok(()) Ok(())
} }
/// Add Preimage
#[inline] #[inline]
pub fn add_preimage(&mut self, preimage: String) { pub fn add_preimage(&mut self, preimage: String) {
self.witness = Some(Witness::HTLCWitness(HTLCWitness { self.witness = Some(Witness::HTLCWitness(HTLCWitness {

View File

@@ -1,3 +1,5 @@
//! Serde helpers for HTLC Witness
use serde::{de, ser, Deserialize, Deserializer, Serializer}; use serde::{de, ser, Deserialize, Deserializer, Serializer};
use super::HTLCWitness; use super::HTLCWitness;

View File

@@ -7,9 +7,11 @@ use serde::{Deserialize, Serialize};
use super::{CurrencyUnit, PaymentMethod}; use super::{CurrencyUnit, PaymentMethod};
use crate::Amount; use crate::Amount;
/// Multi-part payment
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename = "lowercase")] #[serde(rename = "lowercase")]
pub struct Mpp { pub struct Mpp {
/// Amount
pub amount: Amount, pub amount: Amount,
} }
@@ -27,5 +29,6 @@ pub struct MppMethodSettings {
/// Mpp Settings /// Mpp Settings
#[derive(Debug, Default, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] #[derive(Debug, Default, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct Settings { pub struct Settings {
/// Method settings
pub methods: Vec<MppMethodSettings>, pub methods: Vec<MppMethodSettings>,
} }

View File

@@ -1,6 +1,6 @@
//! Secret //! Secret
use core::fmt; use std::fmt;
use std::str::FromStr; use std::str::FromStr;
use bitcoin::secp256k1::rand::{self, RngCore}; use bitcoin::secp256k1::rand::{self, RngCore};
@@ -14,10 +14,13 @@ use crate::util::hex;
#[serde(transparent)] #[serde(transparent)]
pub struct Secret(String); pub struct Secret(String);
/// Secret Errors
#[derive(Debug, Error)] #[derive(Debug, Error)]
pub enum Error { pub enum Error {
/// Invalid Length
#[error("Invalid secret length: `{0}`")] #[error("Invalid secret length: `{0}`")]
InvalidLength(u64), InvalidLength(u64),
/// Hex Error
#[error(transparent)] #[error(transparent)]
Hex(#[from] hex::Error), Hex(#[from] hex::Error),
} }
@@ -29,6 +32,7 @@ impl Default for Secret {
} }
impl Secret { impl Secret {
/// Create new [`Secret`]
#[inline] #[inline]
pub fn new<S>(secret: S) -> Self pub fn new<S>(secret: S) -> Self
where where
@@ -51,16 +55,19 @@ impl Secret {
Self(secret) Self(secret)
} }
/// [`Secret`] as bytes
#[inline] #[inline]
pub fn as_bytes(&self) -> &[u8] { pub fn as_bytes(&self) -> &[u8] {
self.0.as_bytes() self.0.as_bytes()
} }
/// [`Secret`] to bytes
#[inline] #[inline]
pub fn to_bytes(&self) -> Vec<u8> { pub fn to_bytes(&self) -> Vec<u8> {
self.as_bytes().to_vec() self.as_bytes().to_vec()
} }
/// Check if secret is P2PK secret
pub fn is_p2pk(&self) -> bool { pub fn is_p2pk(&self) -> bool {
use crate::nuts::Kind; use crate::nuts::Kind;

View File

@@ -14,24 +14,35 @@ use crate::Amount;
/// Melt response with proofs /// Melt response with proofs
#[derive(Debug, Clone, Hash, PartialEq, Eq, Default, Serialize, Deserialize)] #[derive(Debug, Clone, Hash, PartialEq, Eq, Default, Serialize, Deserialize)]
pub struct Melted { pub struct Melted {
/// State of quote
pub state: MeltQuoteState, pub state: MeltQuoteState,
/// Preimage of melt payment
pub preimage: Option<String>, pub preimage: Option<String>,
/// Melt change
pub change: Option<Proofs>, pub change: Option<Proofs>,
} }
/// Mint Quote Info /// Mint Quote Info
#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
pub struct MintQuote { pub struct MintQuote {
/// Quote id
pub id: String, pub id: String,
/// Mint Url
pub mint_url: UncheckedUrl, pub mint_url: UncheckedUrl,
/// Amount of quote
pub amount: Amount, pub amount: Amount,
/// Unit of quote
pub unit: CurrencyUnit, pub unit: CurrencyUnit,
/// Quote payment request e.g. bolt11
pub request: String, pub request: String,
/// Quote state
pub state: MintQuoteState, pub state: MintQuoteState,
/// Expiration time of quote
pub expiry: u64, pub expiry: u64,
} }
impl MintQuote { impl MintQuote {
/// Create new [`MintQuote`]
pub fn new( pub fn new(
mint_url: UncheckedUrl, mint_url: UncheckedUrl,
request: String, request: String,
@@ -56,18 +67,27 @@ impl MintQuote {
/// Melt Quote Info /// Melt Quote Info
#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
pub struct MeltQuote { pub struct MeltQuote {
/// Quote id
pub id: String, pub id: String,
/// Quote unit
pub unit: CurrencyUnit, pub unit: CurrencyUnit,
/// Quote amount
pub amount: Amount, pub amount: Amount,
/// Quote Payment request e.g. bolt11
pub request: String, pub request: String,
/// Quote fee reserve
pub fee_reserve: Amount, pub fee_reserve: Amount,
/// Quote state
pub state: MeltQuoteState, pub state: MeltQuoteState,
/// Expiration time of quote
pub expiry: u64, pub expiry: u64,
/// Payment preimage
pub payment_preimage: Option<String>, pub payment_preimage: Option<String>,
} }
#[cfg(feature = "mint")] #[cfg(feature = "mint")]
impl MeltQuote { impl MeltQuote {
/// Create new [`MeltQuote`]
pub fn new( pub fn new(
request: String, request: String,
unit: CurrencyUnit, unit: CurrencyUnit,
@@ -90,17 +110,25 @@ impl MeltQuote {
} }
} }
/// Prooinfo
#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
pub struct ProofInfo { pub struct ProofInfo {
/// Proof
pub proof: Proof, pub proof: Proof,
/// y
pub y: PublicKey, pub y: PublicKey,
/// Mint Url
pub mint_url: UncheckedUrl, pub mint_url: UncheckedUrl,
/// Proof State
pub state: State, pub state: State,
/// Proof Spending Conditions
pub spending_condition: Option<SpendingConditions>, pub spending_condition: Option<SpendingConditions>,
/// Unit
pub unit: CurrencyUnit, pub unit: CurrencyUnit,
} }
impl ProofInfo { impl ProofInfo {
/// Create new [`ProofInfo`]
pub fn new( pub fn new(
proof: Proof, proof: Proof,
mint_url: UncheckedUrl, mint_url: UncheckedUrl,
@@ -123,6 +151,7 @@ impl ProofInfo {
}) })
} }
/// Check if [`Proof`] matches conditions
pub fn matches_conditions( pub fn matches_conditions(
&self, &self,
mint_url: &Option<UncheckedUrl>, mint_url: &Option<UncheckedUrl>,

View File

@@ -36,6 +36,7 @@ impl UncheckedUrl {
Self(String::new()) Self(String::new())
} }
/// Join onto url
pub fn join(&self, path: &str) -> Result<Url, Error> { pub fn join(&self, path: &str) -> Result<Url, Error> {
let url: Url = self.try_into()?; let url: Url = self.try_into()?;
Ok(url.join(path)?) Ok(url.join(path)?)

View File

@@ -36,6 +36,7 @@ fn join_url(url: Url, paths: &[&str]) -> Result<Url, Error> {
Ok(url) Ok(url)
} }
/// Http Client
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct HttpClient { pub struct HttpClient {
inner: Client, inner: Client,
@@ -48,6 +49,7 @@ impl Default for HttpClient {
} }
impl HttpClient { impl HttpClient {
/// Create new [`HttpClient`]
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
inner: Client::new(), inner: Client::new(),
@@ -321,6 +323,7 @@ impl HttpClient {
} }
} }
/// Restore request [NUT-13]
#[instrument(skip(self, request), fields(mint_url = %mint_url))] #[instrument(skip(self, request), fields(mint_url = %mint_url))]
pub async fn post_restore( pub async fn post_restore(
&self, &self,

View File

@@ -1,3 +1,5 @@
//! CDK Wallet Error
use std::num::ParseIntError; use std::num::ParseIntError;
use thiserror::Error; use thiserror::Error;
@@ -5,6 +7,7 @@ use thiserror::Error;
use crate::cdk_database; use crate::cdk_database;
use crate::error::{ErrorCode, ErrorResponse}; use crate::error::{ErrorCode, ErrorResponse};
/// Wallet Error
#[derive(Debug, Error)] #[derive(Debug, Error)]
pub enum Error { pub enum Error {
/// Insufficient Funds /// Insufficient Funds
@@ -31,6 +34,7 @@ pub enum Error {
/// Preimage not provided /// Preimage not provided
#[error("Preimage not provided")] #[error("Preimage not provided")]
PreimageNotProvided, PreimageNotProvided,
/// Unknown Key
#[error("Unknown Key")] #[error("Unknown Key")]
UnknownKey, UnknownKey,
/// Spending Locktime not provided /// Spending Locktime not provided
@@ -102,14 +106,6 @@ pub enum Error {
/// Serde Error /// Serde Error
#[error(transparent)] #[error(transparent)]
Serde(#[from] serde_json::Error), 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 /// Custom Error
#[error("`{0}`")] #[error("`{0}`")]
Custom(String), Custom(String),

View File

@@ -34,16 +34,21 @@ pub mod error;
pub mod multi_mint_wallet; pub mod multi_mint_wallet;
pub mod util; pub mod util;
/// CDK Wallet
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Wallet { pub struct Wallet {
/// Mint Url
pub mint_url: UncheckedUrl, pub mint_url: UncheckedUrl,
/// Unit
pub unit: CurrencyUnit, pub unit: CurrencyUnit,
pub client: HttpClient, client: HttpClient,
/// Storage backend
pub localstore: Arc<dyn WalletDatabase<Err = cdk_database::Error> + Send + Sync>, pub localstore: Arc<dyn WalletDatabase<Err = cdk_database::Error> + Send + Sync>,
xpriv: ExtendedPrivKey, xpriv: ExtendedPrivKey,
} }
impl Wallet { impl Wallet {
/// Create new [`Wallet`]
pub fn new( pub fn new(
mint_url: &str, mint_url: &str,
unit: CurrencyUnit, unit: CurrencyUnit,
@@ -278,6 +283,7 @@ impl Wallet {
Ok(()) Ok(())
} }
/// Get Active mint keyset id
#[instrument(skip(self))] #[instrument(skip(self))]
pub async fn active_mint_keyset(&self) -> Result<Id, Error> { pub async fn active_mint_keyset(&self) -> Result<Id, Error> {
let mint_url = &self.mint_url; let mint_url = &self.mint_url;
@@ -307,6 +313,7 @@ impl Wallet {
Err(Error::NoActiveKeyset) Err(Error::NoActiveKeyset)
} }
/// Get active mint keys
#[instrument(skip(self))] #[instrument(skip(self))]
pub async fn active_keys(&self) -> Result<Option<Keys>, Error> { pub async fn active_keys(&self) -> Result<Option<Keys>, Error> {
let active_keyset_id = self.active_mint_keyset().await?; let active_keyset_id = self.active_mint_keyset().await?;
@@ -1044,7 +1051,7 @@ impl Wallet {
Ok(melted) Ok(melted)
} }
// Select proofs /// Select proofs
#[instrument(skip(self))] #[instrument(skip(self))]
pub async fn select_proofs( pub async fn select_proofs(
&self, &self,
@@ -1313,6 +1320,7 @@ impl Wallet {
Ok(total_amount) Ok(total_amount)
} }
/// Restore
#[instrument(skip(self))] #[instrument(skip(self))]
pub async fn restore(&self) -> Result<Amount, Error> { pub async fn restore(&self) -> Result<Amount, Error> {
// Check that mint is in store of mints // Check that mint is in store of mints

View File

@@ -17,11 +17,14 @@ use crate::nuts::{CurrencyUnit, SecretKey, SpendingConditions, Token};
use crate::types::{Melted, MintQuote}; use crate::types::{Melted, MintQuote};
use crate::{Amount, UncheckedUrl, Wallet}; use crate::{Amount, UncheckedUrl, Wallet};
/// Multi Mint Wallet
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct MultiMintWallet { pub struct MultiMintWallet {
/// Wallets
pub wallets: Arc<Mutex<HashMap<WalletKey, Wallet>>>, pub wallets: Arc<Mutex<HashMap<WalletKey, Wallet>>>,
} }
/// Wallet Key
#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
pub struct WalletKey { pub struct WalletKey {
mint_url: UncheckedUrl, mint_url: UncheckedUrl,
@@ -35,6 +38,7 @@ impl fmt::Display for WalletKey {
} }
impl WalletKey { impl WalletKey {
/// Create new [`WalletKey`]
pub fn new(mint_url: UncheckedUrl, unit: CurrencyUnit) -> Self { pub fn new(mint_url: UncheckedUrl, unit: CurrencyUnit) -> Self {
Self { mint_url, unit } Self { mint_url, unit }
} }
@@ -255,7 +259,7 @@ impl MultiMintWallet {
wallet.melt(&quote.id, SplitTarget::default()).await wallet.melt(&quote.id, SplitTarget::default()).await
} }
// Restore /// Restore
#[instrument(skip(self))] #[instrument(skip(self))]
pub async fn restore(&self, wallet_key: &WalletKey) -> Result<Amount, Error> { pub async fn restore(&self, wallet_key: &WalletKey) -> Result<Amount, Error> {
let wallet = self let wallet = self

View File

@@ -27,7 +27,6 @@ pub fn proof_to_token(
Ok(Token::new(mint_url, proofs, memo, unit)?) Ok(Token::new(mint_url, proofs, memo, unit)?)
} }
#[cfg(feature = "nostr")]
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {