Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/wasm_release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ jobs:
- name: Install Rust toolchain
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: 1.86.0
toolchain: 1.87.0

- name: Install wasm-pack
run: |
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
* Call mint endpoint for cancelling
* Use mint nostr relays from network and fall back to identity ones
* Add endpoints to accept, or reject an offer from a mint
* Add logic to check keyset info, mint and create proofs

# 0.3.13

Expand Down
6 changes: 4 additions & 2 deletions crates/bcr-ebill-api/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,11 @@ bcr-ebill-transport = { path = "../bcr-ebill-transport" }
tokio.workspace = true
tokio_with_wasm.workspace = true
secp256k1.workspace = true
bcr-wdc-webapi = { git = "https://github.com/BitcreditProtocol/wildcat", rev = "8a07e1d5012255282acde35982762f18e80322fb" }
bcr-wdc-quote-client = { git = "https://github.com/BitcreditProtocol/wildcat", rev = "8a07e1d5012255282acde35982762f18e80322fb" }
bcr-wdc-webapi = { git = "https://github.com/BitcreditProtocol/wildcat", rev = "4437c3b809105df69ed459a9698cac8deb8d9210" }
bcr-wdc-quote-client = { git = "https://github.com/BitcreditProtocol/wildcat", rev = "4437c3b809105df69ed459a9698cac8deb8d9210" }
bcr-wdc-key-client = { git = "https://github.com/BitcreditProtocol/wildcat", rev = "4437c3b809105df69ed459a9698cac8deb8d9210" }
cashu = { version = "0.9", default-features = false }
rand = { version = "0.8" }

[target.'cfg(target_arch = "wasm32")'.dependencies]
reqwest = { workspace = true, features = ["json"] }
Expand Down
2 changes: 2 additions & 0 deletions crates/bcr-ebill-api/src/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@ pub const VALID_FILE_MIME_TYPES: [&str; 3] = ["image/jpeg", "image/png", "applic

// When subscribing events we subtract this from the last received event time
pub const NOSTR_EVENT_TIME_SLACK: u64 = 3600; // 1 hour
pub const CURRENCY_CRSAT: &str = "crsat";
pub const CURRENCY_SAT: &str = "sat";
130 changes: 128 additions & 2 deletions crates/bcr-ebill-api/src/external/mint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@
contact::{BillAnonParticipant, BillIdentParticipant, BillParticipant, ContactType},
util::{BcrKeys, date::DateTimeUtc},
};
use bcr_wdc_key_client::KeyClient;
use bcr_wdc_quote_client::QuoteClient;
use bcr_wdc_webapi::quotes::{BillInfo, ResolveOffer, StatusReply};
use cashu::{nut01 as cdk01, nut02 as cdk02};
use cashu::{nut00 as cdk00, nut01 as cdk01, nut02 as cdk02};
use thiserror::Error;

/// Generic result type
Expand All @@ -24,6 +25,9 @@
/// all errors originating from parsing public keys
#[error("External Mint Public Key Error")]
PubKey,
/// all errors originating from parsing private keys
#[error("External Mint Private Key Error")]
PrivateKey,
/// all errors originating from creating signatures
#[error("External Mint Signature Error")]
Signature,
Expand All @@ -36,20 +40,40 @@
/// all errors originating from invalid mint request ids
#[error("External Mint Invalid Mint Request Id Error")]
InvalidMintRequestId,
/// all errors originating from invalid keyset ids
#[error("External Mint Invalid KeySet Id Error")]
InvalidKeySetId,
/// all errors originating from blind message generation
#[error("External Mint BlindMessage Error")]
BlindMessage,
/// all errors originating from the quote client
#[error("External Mint Quote Client Error")]
QuoteClient,
/// all errors originating from the key client
#[error("External Mint Key Client Error")]
KeyClient,
}

#[cfg(test)]
use mockall::automock;

use crate::util;
use crate::{constants::CURRENCY_CRSAT, util};

#[cfg_attr(test, automock)]
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
pub trait MintClientApi: ServiceTraitBounds {
/// Mint and return encoded token
async fn mint(
&self,
mint_url: &str,
keyset: cdk02::KeySet,
discounted_amount: u64,
quote_id: &str,
private_key: &str,
) -> Result<String>;
/// Check keyset info for a given keyset id with a given mint
async fn get_keyset_info(&self, mint_url: &str, keyset_id: &str) -> Result<cdk02::KeySet>;
/// Request to mint a bill with a given mint
async fn enquire_mint_quote(
&self,
Expand Down Expand Up @@ -94,11 +118,81 @@
);
Ok(quote_client)
}

pub fn key_client(&self, mint_url: &str) -> Result<KeyClient> {
let key_client = bcr_wdc_key_client::KeyClient::new(
reqwest::Url::parse(mint_url).map_err(|_| Error::InvalidMintUrl)?,

Check warning on line 124 in crates/bcr-ebill-api/src/external/mint.rs

View check run for this annotation

Codecov / codecov/patch

crates/bcr-ebill-api/src/external/mint.rs#L122-L124

Added lines #L122 - L124 were not covered by tests
);
Ok(key_client)
}

Check warning on line 127 in crates/bcr-ebill-api/src/external/mint.rs

View check run for this annotation

Codecov / codecov/patch

crates/bcr-ebill-api/src/external/mint.rs#L126-L127

Added lines #L126 - L127 were not covered by tests
}

#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
impl MintClientApi for MintClient {
async fn mint(
&self,
mint_url: &str,
keyset: cdk02::KeySet,
discounted_amount: u64,
quote_id: &str,
private_key: &str,
) -> Result<String> {
let secret_key = cdk01::SecretKey::from_hex(private_key).map_err(|_| Error::PrivateKey)?;
let qid = uuid::Uuid::from_str(quote_id).map_err(|_| Error::InvalidMintRequestId)?;

Check warning on line 142 in crates/bcr-ebill-api/src/external/mint.rs

View check run for this annotation

Codecov / codecov/patch

crates/bcr-ebill-api/src/external/mint.rs#L140-L142

Added lines #L140 - L142 were not covered by tests

// create blinded messages
let amounts: Vec<cashu::Amount> = cashu::Amount::from(discounted_amount).split();
let blinds = generate_blinds(keyset.id, &amounts)?;
let blinded_messages = blinds.iter().map(|b| b.0.clone()).collect::<Vec<_>>();

Check warning on line 147 in crates/bcr-ebill-api/src/external/mint.rs

View check run for this annotation

Codecov / codecov/patch

crates/bcr-ebill-api/src/external/mint.rs#L145-L147

Added lines #L145 - L147 were not covered by tests

// mint
let blinded_signatures = self
.key_client(mint_url)?
.mint(qid, blinded_messages, secret_key)
.await
.map_err(|e| {
log::error!("Error minting at mint {mint_url}: {e}");
Error::KeyClient
})?;

Check warning on line 157 in crates/bcr-ebill-api/src/external/mint.rs

View check run for this annotation

Codecov / codecov/patch

crates/bcr-ebill-api/src/external/mint.rs#L150-L157

Added lines #L150 - L157 were not covered by tests

// create proofs
let secrets = blinds.iter().map(|b| b.1.clone()).collect::<Vec<_>>();
let rs = blinds.iter().map(|b| b.2.clone()).collect::<Vec<_>>();
let proofs =
cashu::dhke::construct_proofs(blinded_signatures, rs, secrets, &keyset.keys).unwrap();

Check warning on line 163 in crates/bcr-ebill-api/src/external/mint.rs

View check run for this annotation

Codecov / codecov/patch

crates/bcr-ebill-api/src/external/mint.rs#L160-L163

Added lines #L160 - L163 were not covered by tests

// generate token from proofs
let mint_url = cashu::MintUrl::from_str(mint_url).map_err(|_| Error::InvalidMintUrl)?;
let token = cdk00::Token::new(
mint_url,
proofs,
None,
cashu::CurrencyUnit::Custom(CURRENCY_CRSAT.into()),
);

Ok(token.to_v3_string())
}

Check warning on line 175 in crates/bcr-ebill-api/src/external/mint.rs

View check run for this annotation

Codecov / codecov/patch

crates/bcr-ebill-api/src/external/mint.rs#L166-L175

Added lines #L166 - L175 were not covered by tests

async fn get_keyset_info(&self, mint_url: &str, keyset_id: &str) -> Result<cdk02::KeySet> {
let base = reqwest::Url::parse(mint_url).map_err(|_| Error::InvalidMintUrl)?;
let url = base
.join(&format!("/v1/keys/{}", keyset_id))

Check warning

Code scanning / clippy

variables can be used directly in the format! string Warning library

variables can be used directly in the format! string
.expect("keys relative path");
let res = reqwest::Client::new().get(url).send().await.map_err(|e| {
log::error!("Error getting keyset info from mint {mint_url}: {e}");
Error::KeyClient
})?;
let json: cdk01::KeysResponse = res.json().await.map_err(|e| {
log::error!("Error deserializing keyset info: {e}");
Error::KeyClient
})?;
json.keysets.first().map(|k| k.to_owned()).ok_or_else(|| {
log::error!("Empty keyset");
Error::KeyClient.into()
})
}

Check warning on line 194 in crates/bcr-ebill-api/src/external/mint.rs

View check run for this annotation

Codecov / codecov/patch

crates/bcr-ebill-api/src/external/mint.rs#L177-L194

Added lines #L177 - L194 were not covered by tests

async fn enquire_mint_quote(
&self,
mint_url: &str,
Expand Down Expand Up @@ -180,6 +274,38 @@
}
}

pub fn generate_blinds(
keyset_id: cashu::Id,
amounts: &[cashu::Amount],
) -> Result<
Vec<(
cashu::BlindedMessage,
cashu::secret::Secret,
cashu::SecretKey,
)>,
> {
let mut blinds = Vec::new();
for amount in amounts {
let blind = generate_blind(keyset_id, *amount)?;
blinds.push(blind);

Check warning on line 290 in crates/bcr-ebill-api/src/external/mint.rs

View check run for this annotation

Codecov / codecov/patch

crates/bcr-ebill-api/src/external/mint.rs#L277-L290

Added lines #L277 - L290 were not covered by tests
}
Ok(blinds)
}

Check warning on line 293 in crates/bcr-ebill-api/src/external/mint.rs

View check run for this annotation

Codecov / codecov/patch

crates/bcr-ebill-api/src/external/mint.rs#L292-L293

Added lines #L292 - L293 were not covered by tests

pub fn generate_blind(
kid: cashu::Id,
amount: cashu::Amount,
) -> Result<(
cashu::BlindedMessage,
cashu::secret::Secret,
cashu::SecretKey,
)> {
let secret = cashu::secret::Secret::new(rand::random::<u64>().to_string());
let (b_, r) =
cashu::dhke::blind_message(secret.as_bytes(), None).map_err(|_| Error::BlindMessage)?;
Ok((cashu::BlindedMessage::new(amount, kid, b_), secret, r))
}

Check warning on line 307 in crates/bcr-ebill-api/src/external/mint.rs

View check run for this annotation

Codecov / codecov/patch

crates/bcr-ebill-api/src/external/mint.rs#L295-L307

Added lines #L295 - L307 were not covered by tests

#[derive(Debug, Clone)]
pub enum ResolveMintOffer {
Accept,
Expand Down
Loading
Loading