mirror of
https://github.com/aljazceru/cdk.git
synced 2026-02-06 13:45:53 +01:00
add feature flags for mint and wallet
This commit is contained in:
@@ -5,6 +5,7 @@ edition = "2021"
|
||||
authors = ["thesimplekid"]
|
||||
license = "BSD-3-Clause"
|
||||
readme = "README.md"
|
||||
documentation = "https://docs.rs/crate/cashu-crab"
|
||||
repository = "https://github.com/thesimplekid/cashu-crab"
|
||||
description = "Cashu rust wallet and mint library"
|
||||
# exclude = ["integration_test"]
|
||||
@@ -12,6 +13,10 @@ description = "Cashu rust wallet and mint library"
|
||||
#[workspace]
|
||||
#members = ["integration_test"]
|
||||
|
||||
[features]
|
||||
default = ["mint", "wallet"]
|
||||
mint = []
|
||||
wallet = []
|
||||
|
||||
[dependencies]
|
||||
base64 = "0.21.0"
|
||||
|
||||
@@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
use url::Url;
|
||||
|
||||
use crate::nuts::nut00::{BlindedMessage, BlindedMessages, Proof};
|
||||
use crate::nuts::nut00::{wallet::BlindedMessages, BlindedMessage, Proof};
|
||||
use crate::nuts::nut01::Keys;
|
||||
use crate::nuts::nut03::RequestMintResponse;
|
||||
use crate::nuts::nut04::{MintRequest, PostMintResponse};
|
||||
|
||||
42
src/dhke.rs
42
src/dhke.rs
@@ -4,14 +4,21 @@ use std::ops::Mul;
|
||||
|
||||
use bitcoin_hashes::sha256;
|
||||
use bitcoin_hashes::Hash;
|
||||
use k256::{ProjectivePoint, Scalar, SecretKey};
|
||||
|
||||
use crate::error::Error;
|
||||
use crate::nuts::nut00::BlindedSignature;
|
||||
use crate::nuts::nut00::Proof;
|
||||
use crate::nuts::nut00::Proofs;
|
||||
use crate::nuts::nut01::Keys;
|
||||
use crate::nuts::nut01::PublicKey;
|
||||
#[cfg(feature = "wallet")]
|
||||
use k256::ProjectivePoint;
|
||||
|
||||
use k256::{Scalar, SecretKey};
|
||||
|
||||
use crate::error;
|
||||
|
||||
#[cfg(feature = "wallet")]
|
||||
use crate::nuts::nut00::{BlindedSignature, Proof, Proofs};
|
||||
|
||||
#[cfg(feature = "wallet")]
|
||||
use crate::nuts::nut01::{Keys, PublicKey};
|
||||
|
||||
#[cfg(feature = "wallet")]
|
||||
use crate::nuts::*;
|
||||
|
||||
fn hash_to_curve(message: &[u8]) -> k256::PublicKey {
|
||||
@@ -32,11 +39,12 @@ fn hash_to_curve(message: &[u8]) -> k256::PublicKey {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "wallet")]
|
||||
/// Blind Message Alice Step one
|
||||
pub fn blind_message(
|
||||
secret: &[u8],
|
||||
blinding_factor: Option<SecretKey>,
|
||||
) -> Result<(PublicKey, SecretKey), Error> {
|
||||
) -> Result<(PublicKey, SecretKey), error::wallet::Error> {
|
||||
let y = hash_to_curve(secret);
|
||||
|
||||
let r: SecretKey = match blinding_factor {
|
||||
@@ -49,6 +57,7 @@ pub fn blind_message(
|
||||
Ok((k256::PublicKey::try_from(b)?.into(), r))
|
||||
}
|
||||
|
||||
#[cfg(feature = "wallet")]
|
||||
/// Unblind Message (Alice Step 3)
|
||||
pub fn unblind_message(
|
||||
// C_
|
||||
@@ -56,7 +65,7 @@ pub fn unblind_message(
|
||||
r: SecretKey,
|
||||
// A
|
||||
mint_pubkey: PublicKey,
|
||||
) -> Result<PublicKey, Error> {
|
||||
) -> Result<PublicKey, error::wallet::Error> {
|
||||
// C
|
||||
// Unblinded message
|
||||
let c = ProjectivePoint::from(Into::<k256::PublicKey>::into(blinded_key).as_affine())
|
||||
@@ -67,19 +76,22 @@ pub fn unblind_message(
|
||||
Ok(k256::PublicKey::try_from(c)?.into())
|
||||
}
|
||||
|
||||
#[cfg(feature = "wallet")]
|
||||
/// Construct Proof
|
||||
pub fn construct_proofs(
|
||||
promises: Vec<BlindedSignature>,
|
||||
rs: Vec<nut01::SecretKey>,
|
||||
secrets: Vec<String>,
|
||||
keys: &Keys,
|
||||
) -> Result<Proofs, Error> {
|
||||
) -> Result<Proofs, error::wallet::Error> {
|
||||
let mut proofs = vec![];
|
||||
for (i, promise) in promises.into_iter().enumerate() {
|
||||
let blinded_c = promise.c;
|
||||
let a: PublicKey = keys
|
||||
.amount_key(promise.amount)
|
||||
.ok_or(Error::CustomError("Could not get proofs".to_string()))?
|
||||
.ok_or(error::wallet::Error::CustomError(
|
||||
"Could not get proofs".to_string(),
|
||||
))?
|
||||
.to_owned();
|
||||
|
||||
let unblinded_signature = unblind_message(blinded_c, rs[i].clone().into(), a)?;
|
||||
@@ -98,11 +110,12 @@ pub fn construct_proofs(
|
||||
Ok(proofs)
|
||||
}
|
||||
|
||||
#[cfg(feature = "mint")]
|
||||
/// Sign Blinded Message (Step2 bob)
|
||||
pub fn sign_message(
|
||||
a: SecretKey,
|
||||
blinded_message: k256::PublicKey,
|
||||
) -> Result<k256::PublicKey, Error> {
|
||||
) -> Result<k256::PublicKey, error::mint::Error> {
|
||||
Ok(k256::PublicKey::try_from(
|
||||
blinded_message
|
||||
.as_affine()
|
||||
@@ -110,12 +123,13 @@ pub fn sign_message(
|
||||
)?)
|
||||
}
|
||||
|
||||
#[cfg(feature = "mint")]
|
||||
/// Verify Message
|
||||
pub fn verify_message(
|
||||
a: SecretKey,
|
||||
unblinded_message: k256::PublicKey,
|
||||
msg: &str,
|
||||
) -> Result<(), Error> {
|
||||
) -> Result<(), error::mint::Error> {
|
||||
// Y
|
||||
let y = hash_to_curve(msg.as_bytes());
|
||||
|
||||
@@ -125,7 +139,7 @@ pub fn verify_message(
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
Err(Error::TokenNotVerifed)
|
||||
Err(error::mint::Error::TokenNotVerifed)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
132
src/error.rs
132
src/error.rs
@@ -4,53 +4,37 @@ use std::string::FromUtf8Error;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
/// Min req error
|
||||
MinReqError(minreq::Error),
|
||||
/// Parse Url Error
|
||||
UrlParseError(url::ParseError),
|
||||
/// Unsupported Token
|
||||
UnsupportedToken,
|
||||
/// Utf8 parse error
|
||||
Utf8ParseError(FromUtf8Error),
|
||||
/// Serde Json error
|
||||
SerdeJsonError(serde_json::Error),
|
||||
/// Base64 error
|
||||
Base64Error(base64::DecodeError),
|
||||
/// Insufficaint Funds
|
||||
InsufficantFunds,
|
||||
CustomError(String),
|
||||
/// From hex error
|
||||
HexError(hex::FromHexError),
|
||||
/// From elliptic curve
|
||||
EllipticError(k256::elliptic_curve::Error),
|
||||
AmountKey,
|
||||
Amount,
|
||||
TokenSpent,
|
||||
TokenNotVerifed,
|
||||
OutputOrdering,
|
||||
InvoiceAmountUndefined,
|
||||
CrabMintError(crate::client::Error),
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Error::MinReqError(err) => write!(f, "{}", err),
|
||||
Error::UrlParseError(err) => write!(f, "{}", err),
|
||||
Error::UnsupportedToken => write!(f, "Unsuppported Token"),
|
||||
Error::Utf8ParseError(err) => write!(f, "{}", err),
|
||||
Error::SerdeJsonError(err) => write!(f, "{}", err),
|
||||
Error::Base64Error(err) => write!(f, "{}", err),
|
||||
Error::InsufficantFunds => write!(f, "Insufficant Funds"),
|
||||
Error::CustomError(err) => write!(f, "{}", err),
|
||||
Error::HexError(err) => write!(f, "{}", err),
|
||||
Error::EllipticError(err) => write!(f, "{}", err),
|
||||
Error::AmountKey => write!(f, "No Key for amount"),
|
||||
Error::Amount => write!(f, "Amount miss match"),
|
||||
Error::TokenSpent => write!(f, "Token Spent"),
|
||||
Error::TokenNotVerifed => write!(f, "Token Not Verified"),
|
||||
Error::CrabMintError(err) => write!(f, "{}", err),
|
||||
Error::OutputOrdering => write!(f, "Output ordering"),
|
||||
Error::InvoiceAmountUndefined => write!(f, "Invoice without amount"),
|
||||
}
|
||||
}
|
||||
@@ -58,12 +42,6 @@ impl fmt::Display for Error {
|
||||
|
||||
impl StdError for Error {}
|
||||
|
||||
impl From<minreq::Error> for Error {
|
||||
fn from(err: minreq::Error) -> Error {
|
||||
Error::MinReqError(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<url::ParseError> for Error {
|
||||
fn from(err: url::ParseError) -> Error {
|
||||
Error::UrlParseError(err)
|
||||
@@ -94,14 +72,112 @@ impl From<hex::FromHexError> for Error {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<k256::elliptic_curve::Error> for Error {
|
||||
fn from(err: k256::elliptic_curve::Error) -> Error {
|
||||
Error::EllipticError(err)
|
||||
#[cfg(feature = "wallet")]
|
||||
pub mod wallet {
|
||||
use std::error::Error as StdError;
|
||||
use std::fmt;
|
||||
use std::string::FromUtf8Error;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
CrabMintError(crate::client::Error),
|
||||
/// Serde Json error
|
||||
SerdeJsonError(serde_json::Error),
|
||||
/// From elliptic curve
|
||||
EllipticError(k256::elliptic_curve::Error),
|
||||
/// Insufficaint Funds
|
||||
InsufficantFunds,
|
||||
/// Utf8 parse error
|
||||
Utf8ParseError(FromUtf8Error),
|
||||
/// Base64 error
|
||||
Base64Error(base64::DecodeError),
|
||||
/// Unsupported Token
|
||||
UnsupportedToken,
|
||||
CustomError(String),
|
||||
}
|
||||
|
||||
impl StdError for Error {}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Error::CrabMintError(err) => write!(f, "{}", err),
|
||||
Error::CustomError(err) => write!(f, "{}", err),
|
||||
Error::InsufficantFunds => write!(f, "Insufficant Funds"),
|
||||
Error::Utf8ParseError(err) => write!(f, "{}", err),
|
||||
Error::Base64Error(err) => write!(f, "{}", err),
|
||||
Error::UnsupportedToken => write!(f, "Unsuppported Token"),
|
||||
Error::EllipticError(err) => write!(f, "{}", err),
|
||||
Error::SerdeJsonError(err) => write!(f, "{}", err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<crate::client::Error> for Error {
|
||||
fn from(err: crate::client::Error) -> Error {
|
||||
Error::CrabMintError(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<serde_json::Error> for Error {
|
||||
fn from(err: serde_json::Error) -> Error {
|
||||
Error::SerdeJsonError(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<k256::elliptic_curve::Error> for Error {
|
||||
fn from(err: k256::elliptic_curve::Error) -> Error {
|
||||
Error::EllipticError(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<FromUtf8Error> for Error {
|
||||
fn from(err: FromUtf8Error) -> Error {
|
||||
Error::Utf8ParseError(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<base64::DecodeError> for Error {
|
||||
fn from(err: base64::DecodeError) -> Error {
|
||||
Error::Base64Error(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<crate::client::Error> for Error {
|
||||
fn from(err: crate::client::Error) -> Error {
|
||||
Error::CrabMintError(err)
|
||||
#[cfg(feature = "mint")]
|
||||
pub mod mint {
|
||||
use std::error::Error as StdError;
|
||||
use std::fmt;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
AmountKey,
|
||||
Amount,
|
||||
TokenSpent,
|
||||
/// From elliptic curve
|
||||
EllipticError(k256::elliptic_curve::Error),
|
||||
TokenNotVerifed,
|
||||
InvoiceAmountUndefined,
|
||||
}
|
||||
|
||||
impl StdError for Error {}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Error::AmountKey => write!(f, "No Key for amount"),
|
||||
Error::Amount => write!(f, "Amount miss match"),
|
||||
Error::TokenSpent => write!(f, "Token Spent"),
|
||||
Error::EllipticError(err) => write!(f, "{}", err),
|
||||
Error::TokenNotVerifed => write!(f, "Token Not Verified"),
|
||||
Error::InvoiceAmountUndefined => write!(f, "Invoice without amount"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<k256::elliptic_curve::Error> for Error {
|
||||
fn from(err: k256::elliptic_curve::Error) -> Error {
|
||||
Error::EllipticError(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
pub mod amount;
|
||||
#[cfg(feature = "wallet")]
|
||||
pub mod client;
|
||||
pub mod dhke;
|
||||
pub mod error;
|
||||
#[cfg(feature = "mint")]
|
||||
pub mod mint;
|
||||
pub mod nuts;
|
||||
pub mod serde_utils;
|
||||
pub mod types;
|
||||
pub mod utils;
|
||||
#[cfg(feature = "wallet")]
|
||||
pub mod wallet;
|
||||
|
||||
pub use amount::Amount;
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::collections::{HashMap, HashSet};
|
||||
|
||||
use crate::dhke::sign_message;
|
||||
use crate::dhke::verify_message;
|
||||
use crate::error::Error;
|
||||
use crate::error::mint::Error;
|
||||
use crate::nuts::nut00::BlindedMessage;
|
||||
use crate::nuts::nut00::BlindedSignature;
|
||||
use crate::nuts::nut00::Proof;
|
||||
@@ -169,7 +169,11 @@ impl Mint {
|
||||
let proofs_total = melt_request.proofs_amount();
|
||||
|
||||
// TODO: Fee reserve
|
||||
if proofs_total < melt_request.invoice_amount()? {
|
||||
if proofs_total
|
||||
< melt_request
|
||||
.invoice_amount()
|
||||
.map_err(|_| Error::InvoiceAmountUndefined)?
|
||||
{
|
||||
return Err(Error::Amount);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,17 +1,13 @@
|
||||
//! Notation and Models
|
||||
// https://github.com/cashubtc/nuts/blob/main/00.md
|
||||
|
||||
use std::str::FromStr;
|
||||
|
||||
use base64::{engine::general_purpose, Engine as _};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use url::Url;
|
||||
|
||||
use crate::utils::generate_secret;
|
||||
use crate::serde_utils::serde_url;
|
||||
use crate::Amount;
|
||||
use crate::{dhke::blind_message, error::Error, serde_utils::serde_url, utils::split_amount};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::nut01::{self, PublicKey};
|
||||
use super::nut01::PublicKey;
|
||||
|
||||
/// Blinded Message [NUT-00]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
@@ -23,67 +19,155 @@ pub struct BlindedMessage {
|
||||
pub b: PublicKey,
|
||||
}
|
||||
|
||||
/// Blinded Messages [NUT-00]
|
||||
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct BlindedMessages {
|
||||
/// Blinded messages
|
||||
pub blinded_messages: Vec<BlindedMessage>,
|
||||
/// Secrets
|
||||
pub secrets: Vec<String>,
|
||||
/// Rs
|
||||
pub rs: Vec<nut01::SecretKey>,
|
||||
/// Amounts
|
||||
pub amounts: Vec<Amount>,
|
||||
}
|
||||
#[cfg(feature = "wallet")]
|
||||
pub mod wallet {
|
||||
use std::str::FromStr;
|
||||
|
||||
impl BlindedMessages {
|
||||
/// Outputs for speceifed amount with random secret
|
||||
pub fn random(amount: Amount) -> Result<Self, Error> {
|
||||
let mut blinded_messages = BlindedMessages::default();
|
||||
use base64::{engine::general_purpose, Engine as _};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use url::Url;
|
||||
|
||||
for amount in split_amount(amount) {
|
||||
let secret = generate_secret();
|
||||
let (blinded, r) = blind_message(secret.as_bytes(), None)?;
|
||||
use crate::error;
|
||||
use crate::error::wallet;
|
||||
use crate::nuts::nut00::BlindedMessage;
|
||||
use crate::nuts::nut00::Proofs;
|
||||
use crate::nuts::nut01;
|
||||
use crate::utils::generate_secret;
|
||||
use crate::Amount;
|
||||
use crate::{dhke::blind_message, utils::split_amount};
|
||||
|
||||
let blinded_message = BlindedMessage { amount, b: blinded };
|
||||
use super::MintProofs;
|
||||
|
||||
blinded_messages.secrets.push(secret);
|
||||
blinded_messages.blinded_messages.push(blinded_message);
|
||||
blinded_messages.rs.push(r.into());
|
||||
blinded_messages.amounts.push(amount);
|
||||
}
|
||||
|
||||
Ok(blinded_messages)
|
||||
/// Blinded Messages [NUT-00]
|
||||
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct BlindedMessages {
|
||||
/// Blinded messages
|
||||
pub blinded_messages: Vec<BlindedMessage>,
|
||||
/// Secrets
|
||||
pub secrets: Vec<String>,
|
||||
/// Rs
|
||||
pub rs: Vec<nut01::SecretKey>,
|
||||
/// Amounts
|
||||
pub amounts: Vec<Amount>,
|
||||
}
|
||||
|
||||
/// Blank Outputs used for NUT-08 change
|
||||
pub fn blank(fee_reserve: Amount) -> Result<Self, Error> {
|
||||
let mut blinded_messages = BlindedMessages::default();
|
||||
impl BlindedMessages {
|
||||
/// Outputs for speceifed amount with random secret
|
||||
pub fn random(amount: Amount) -> Result<Self, wallet::Error> {
|
||||
let mut blinded_messages = BlindedMessages::default();
|
||||
|
||||
let fee_reserve = bitcoin::Amount::from_sat(fee_reserve.to_sat());
|
||||
for amount in split_amount(amount) {
|
||||
let secret = generate_secret();
|
||||
let (blinded, r) = blind_message(secret.as_bytes(), None)?;
|
||||
|
||||
let count = (fee_reserve
|
||||
.to_float_in(bitcoin::Denomination::Satoshi)
|
||||
.log2()
|
||||
.ceil() as u64)
|
||||
.max(1);
|
||||
let blinded_message = BlindedMessage { amount, b: blinded };
|
||||
|
||||
for _i in 0..count {
|
||||
let secret = generate_secret();
|
||||
let (blinded, r) = blind_message(secret.as_bytes(), None)?;
|
||||
blinded_messages.secrets.push(secret);
|
||||
blinded_messages.blinded_messages.push(blinded_message);
|
||||
blinded_messages.rs.push(r.into());
|
||||
blinded_messages.amounts.push(amount);
|
||||
}
|
||||
|
||||
let blinded_message = BlindedMessage {
|
||||
amount: Amount::ZERO,
|
||||
b: blinded,
|
||||
};
|
||||
|
||||
blinded_messages.secrets.push(secret);
|
||||
blinded_messages.blinded_messages.push(blinded_message);
|
||||
blinded_messages.rs.push(r.into());
|
||||
blinded_messages.amounts.push(Amount::ZERO);
|
||||
Ok(blinded_messages)
|
||||
}
|
||||
|
||||
Ok(blinded_messages)
|
||||
/// Blank Outputs used for NUT-08 change
|
||||
pub fn blank(fee_reserve: Amount) -> Result<Self, wallet::Error> {
|
||||
let mut blinded_messages = BlindedMessages::default();
|
||||
|
||||
let fee_reserve = bitcoin::Amount::from_sat(fee_reserve.to_sat());
|
||||
|
||||
let count = (fee_reserve
|
||||
.to_float_in(bitcoin::Denomination::Satoshi)
|
||||
.log2()
|
||||
.ceil() as u64)
|
||||
.max(1);
|
||||
|
||||
for _i in 0..count {
|
||||
let secret = generate_secret();
|
||||
let (blinded, r) = blind_message(secret.as_bytes(), None)?;
|
||||
|
||||
let blinded_message = BlindedMessage {
|
||||
amount: Amount::ZERO,
|
||||
b: blinded,
|
||||
};
|
||||
|
||||
blinded_messages.secrets.push(secret);
|
||||
blinded_messages.blinded_messages.push(blinded_message);
|
||||
blinded_messages.rs.push(r.into());
|
||||
blinded_messages.amounts.push(Amount::ZERO);
|
||||
}
|
||||
|
||||
Ok(blinded_messages)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct Token {
|
||||
pub token: Vec<MintProofs>,
|
||||
pub memo: Option<String>,
|
||||
}
|
||||
|
||||
impl Token {
|
||||
pub fn new(mint_url: Url, proofs: Proofs, memo: Option<String>) -> Self {
|
||||
Self {
|
||||
token: vec![MintProofs::new(mint_url, proofs)],
|
||||
memo,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn token_info(&self) -> (u64, String) {
|
||||
let mut amount = Amount::ZERO;
|
||||
|
||||
for proofs in &self.token {
|
||||
for proof in &proofs.proofs {
|
||||
amount += proof.amount;
|
||||
}
|
||||
}
|
||||
|
||||
(amount.to_sat(), self.token[0].mint.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Token {
|
||||
type Err = error::wallet::Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
if !s.starts_with("cashuA") {
|
||||
return Err(wallet::Error::UnsupportedToken);
|
||||
}
|
||||
|
||||
let s = s.replace("cashuA", "");
|
||||
let decoded = general_purpose::STANDARD.decode(s)?;
|
||||
let decoded_str = String::from_utf8(decoded)?;
|
||||
let token: Token = serde_json::from_str(&decoded_str)?;
|
||||
Ok(token)
|
||||
}
|
||||
}
|
||||
|
||||
impl Token {
|
||||
pub fn convert_to_string(&self) -> Result<String, wallet::Error> {
|
||||
let json_string = serde_json::to_string(self)?;
|
||||
let encoded = general_purpose::STANDARD.encode(json_string);
|
||||
Ok(format!("cashuA{}", encoded))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct MintProofs {
|
||||
#[serde(with = "serde_url")]
|
||||
pub mint: Url,
|
||||
pub proofs: Proofs,
|
||||
}
|
||||
|
||||
#[cfg(feature = "wallet")]
|
||||
impl MintProofs {
|
||||
fn new(mint_url: Url, proofs: Proofs) -> Self {
|
||||
Self {
|
||||
mint: mint_url,
|
||||
proofs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,77 +214,6 @@ impl From<Proof> for mint::Proof {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mint_proofs_from_proofs(proofs: Proofs) -> mint::Proofs {
|
||||
proofs.iter().map(|p| p.to_owned().into()).collect()
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct MintProofs {
|
||||
#[serde(with = "serde_url")]
|
||||
pub mint: Url,
|
||||
pub proofs: Proofs,
|
||||
}
|
||||
|
||||
impl MintProofs {
|
||||
fn new(mint_url: Url, proofs: Proofs) -> Self {
|
||||
Self {
|
||||
mint: mint_url,
|
||||
proofs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct Token {
|
||||
pub token: Vec<MintProofs>,
|
||||
pub memo: Option<String>,
|
||||
}
|
||||
|
||||
impl Token {
|
||||
pub fn new(mint_url: Url, proofs: Proofs, memo: Option<String>) -> Self {
|
||||
Self {
|
||||
token: vec![MintProofs::new(mint_url, proofs)],
|
||||
memo,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn token_info(&self) -> (u64, String) {
|
||||
let mut amount = Amount::ZERO;
|
||||
|
||||
for proofs in &self.token {
|
||||
for proof in &proofs.proofs {
|
||||
amount += proof.amount;
|
||||
}
|
||||
}
|
||||
|
||||
(amount.to_sat(), self.token[0].mint.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Token {
|
||||
type Err = Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
if !s.starts_with("cashuA") {
|
||||
return Err(Error::UnsupportedToken);
|
||||
}
|
||||
|
||||
let s = s.replace("cashuA", "");
|
||||
let decoded = general_purpose::STANDARD.decode(s)?;
|
||||
let decoded_str = String::from_utf8(decoded)?;
|
||||
let token: Token = serde_json::from_str(&decoded_str)?;
|
||||
Ok(token)
|
||||
}
|
||||
}
|
||||
|
||||
impl Token {
|
||||
pub fn convert_to_string(&self) -> Result<String, Error> {
|
||||
let json_string = serde_json::to_string(self)?;
|
||||
let encoded = general_purpose::STANDARD.encode(json_string);
|
||||
Ok(format!("cashuA{}", encoded))
|
||||
}
|
||||
}
|
||||
|
||||
pub mod mint {
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@@ -228,10 +241,18 @@ pub mod mint {
|
||||
|
||||
/// List of proofs
|
||||
pub type Proofs = Vec<Proof>;
|
||||
|
||||
pub fn mint_proofs_from_proofs(proofs: super::Proofs) -> Proofs {
|
||||
proofs.iter().map(|p| p.to_owned().into()).collect()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::str::FromStr;
|
||||
use url::Url;
|
||||
|
||||
use super::wallet::*;
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -2,11 +2,15 @@
|
||||
// https://github.com/cashubtc/nuts/blob/main/06.md
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::nuts::nut00::{BlindedMessage, BlindedMessages, Proofs};
|
||||
use crate::nuts::nut00::{BlindedMessage, Proofs};
|
||||
use crate::Amount;
|
||||
|
||||
#[cfg(feature = "wallet")]
|
||||
use crate::nuts::nut00::wallet::BlindedMessages;
|
||||
|
||||
use super::nut00::BlindedSignature;
|
||||
|
||||
#[cfg(feature = "wallet")]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct SplitPayload {
|
||||
pub blinded_messages: BlindedMessages,
|
||||
|
||||
@@ -2,14 +2,16 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use crate::dhke::unblind_message;
|
||||
use crate::nuts::nut00::{mint, BlindedMessages, BlindedSignature, Proof, Proofs, Token};
|
||||
use crate::nuts::nut00::{
|
||||
mint, wallet::BlindedMessages, wallet::Token, BlindedSignature, Proof, Proofs,
|
||||
};
|
||||
use crate::nuts::nut01::Keys;
|
||||
use crate::nuts::nut03::RequestMintResponse;
|
||||
use crate::nuts::nut06::{SplitPayload, SplitRequest};
|
||||
use crate::types::{Melted, ProofsStatus, SendProofs};
|
||||
use crate::Amount;
|
||||
pub use crate::Invoice;
|
||||
use crate::{client::Client, dhke::construct_proofs, error::Error};
|
||||
use crate::{client::Client, dhke::construct_proofs, error};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Wallet {
|
||||
@@ -30,7 +32,10 @@ impl Wallet {
|
||||
// TODO: getter method for keys that if it cant get them try again
|
||||
|
||||
/// Check if a proof is spent
|
||||
pub async fn check_proofs_spent(&self, proofs: &mint::Proofs) -> Result<ProofsStatus, Error> {
|
||||
pub async fn check_proofs_spent(
|
||||
&self,
|
||||
proofs: &mint::Proofs,
|
||||
) -> Result<ProofsStatus, error::wallet::Error> {
|
||||
let spendable = self.client.check_spendable(proofs).await?;
|
||||
|
||||
// Separate proofs in spent and unspent based on mint response
|
||||
@@ -46,12 +51,19 @@ impl Wallet {
|
||||
}
|
||||
|
||||
/// Request Token Mint
|
||||
pub async fn request_mint(&self, amount: Amount) -> Result<RequestMintResponse, Error> {
|
||||
pub async fn request_mint(
|
||||
&self,
|
||||
amount: Amount,
|
||||
) -> Result<RequestMintResponse, error::wallet::Error> {
|
||||
Ok(self.client.request_mint(amount).await?)
|
||||
}
|
||||
|
||||
/// Mint Token
|
||||
pub async fn mint_token(&self, amount: Amount, hash: &str) -> Result<Token, Error> {
|
||||
pub async fn mint_token(
|
||||
&self,
|
||||
amount: Amount,
|
||||
hash: &str,
|
||||
) -> Result<Token, error::wallet::Error> {
|
||||
let proofs = self.mint(amount, hash).await?;
|
||||
|
||||
let token = Token::new(self.client.mint_url.clone(), proofs, None);
|
||||
@@ -59,7 +71,7 @@ impl Wallet {
|
||||
}
|
||||
|
||||
/// Mint Proofs
|
||||
pub async fn mint(&self, amount: Amount, hash: &str) -> Result<Proofs, Error> {
|
||||
pub async fn mint(&self, amount: Amount, hash: &str) -> Result<Proofs, error::wallet::Error> {
|
||||
let blinded_messages = BlindedMessages::random(amount)?;
|
||||
|
||||
let mint_res = self.client.mint(blinded_messages.clone(), hash).await?;
|
||||
@@ -75,12 +87,12 @@ impl Wallet {
|
||||
}
|
||||
|
||||
/// Check fee
|
||||
pub async fn check_fee(&self, invoice: Invoice) -> Result<Amount, Error> {
|
||||
pub async fn check_fee(&self, invoice: Invoice) -> Result<Amount, error::wallet::Error> {
|
||||
Ok(self.client.check_fees(invoice).await?.fee)
|
||||
}
|
||||
|
||||
/// Receive
|
||||
pub async fn receive(&self, encoded_token: &str) -> Result<Proofs, Error> {
|
||||
pub async fn receive(&self, encoded_token: &str) -> Result<Proofs, error::wallet::Error> {
|
||||
let token_data = Token::from_str(encoded_token)?;
|
||||
|
||||
let mut proofs: Vec<Proofs> = vec![vec![]];
|
||||
@@ -113,7 +125,7 @@ impl Wallet {
|
||||
}
|
||||
|
||||
/// Create Split Payload
|
||||
fn create_split(&self, proofs: Proofs) -> Result<SplitPayload, Error> {
|
||||
fn create_split(&self, proofs: Proofs) -> Result<SplitPayload, error::wallet::Error> {
|
||||
let value = proofs.iter().map(|p| p.amount).sum();
|
||||
|
||||
let blinded_messages = BlindedMessages::random(value)?;
|
||||
@@ -133,7 +145,7 @@ impl Wallet {
|
||||
&self,
|
||||
blinded_messages: BlindedMessages,
|
||||
promises: Vec<BlindedSignature>,
|
||||
) -> Result<Proofs, Error> {
|
||||
) -> Result<Proofs, error::wallet::Error> {
|
||||
let BlindedMessages {
|
||||
blinded_messages: _,
|
||||
secrets,
|
||||
@@ -169,7 +181,11 @@ impl Wallet {
|
||||
}
|
||||
|
||||
/// Send
|
||||
pub async fn send(&self, amount: Amount, proofs: Proofs) -> Result<SendProofs, Error> {
|
||||
pub async fn send(
|
||||
&self,
|
||||
amount: Amount,
|
||||
proofs: Proofs,
|
||||
) -> Result<SendProofs, error::wallet::Error> {
|
||||
let mut amount_available = Amount::ZERO;
|
||||
let mut send_proofs = SendProofs::default();
|
||||
|
||||
@@ -185,7 +201,7 @@ impl Wallet {
|
||||
|
||||
if amount_available.lt(&amount) {
|
||||
println!("Not enough funds");
|
||||
return Err(Error::InsufficantFunds);
|
||||
return Err(error::wallet::Error::InsufficantFunds);
|
||||
}
|
||||
|
||||
// If amount available is EQUAL to send amount no need to split
|
||||
@@ -227,7 +243,7 @@ impl Wallet {
|
||||
invoice: Invoice,
|
||||
proofs: Proofs,
|
||||
fee_reserve: Amount,
|
||||
) -> Result<Melted, Error> {
|
||||
) -> Result<Melted, error::wallet::Error> {
|
||||
let blinded = BlindedMessages::blank(fee_reserve)?;
|
||||
let melt_response = self
|
||||
.client
|
||||
@@ -253,7 +269,11 @@ impl Wallet {
|
||||
Ok(melted)
|
||||
}
|
||||
|
||||
pub fn proofs_to_token(&self, proofs: Proofs, memo: Option<String>) -> Result<String, Error> {
|
||||
pub fn proofs_to_token(
|
||||
&self,
|
||||
proofs: Proofs,
|
||||
memo: Option<String>,
|
||||
) -> Result<String, error::wallet::Error> {
|
||||
Token::new(self.client.mint_url.clone(), proofs, memo).convert_to_string()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user